diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bd2cc31aa6e..ab507c274172 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,8 +148,8 @@ jobs: version=`echo $version | sed 's/-/./g'` major=`echo $version | cut -d. -f1` minor=`echo $version | cut -d. -f2` - echo "version_file=CHANGELOG/CHANGELOG-$major.$minor.md" - echo "version_file=CHANGELOG/CHANGELOG-$major.$minor.md" >> $GITHUB_OUTPUT + echo "version_file=CHANGELOG/zh_CN/CHANGELOG-$major.$minor.md" + echo "version_file=CHANGELOG/zh_CN/CHANGELOG-$major.$minor.md" >> $GITHUB_OUTPUT - name: Create Release id: create_release uses: ncipollo/release-action@v1.12.0 diff --git a/CHANGELOG/README_en.md b/CHANGELOG/README_en.md index 2a0c85e4ba51..e373c407e8dc 100644 --- a/CHANGELOG/README_en.md +++ b/CHANGELOG/README_en.md @@ -3,3 +3,4 @@ - [CHANGELOG-2.0.md](en/CHANGELOG-2.0.md) - [CHANGELOG-2.1.md](en/CHANGELOG-2.1.md) - [CHANGELOG-3.0.md](en/CHANGELOG-3.0.md) +- [CHANGELOG-3.1.md](en/CHANGELOG-3.1.md) diff --git a/CHANGELOG/en/CHANGELOG-2.0.md b/CHANGELOG/en/CHANGELOG-2.0.md index c93f53b828bb..8ce4542ae7e8 100644 --- a/CHANGELOG/en/CHANGELOG-2.0.md +++ b/CHANGELOG/en/CHANGELOG-2.0.md @@ -18,7 +18,7 @@ # v2.0.3 ## 2024-05-28 -## Changelog since v2.0.2 +### Changelog since v2.0.2 #### New - [New] Modification of the login failure pop-up window specification [link](http://github.com/TencentBlueKing/bk-ci/issues/8125) @@ -28,19 +28,19 @@ # v2.0.2 ## 2024-03-04 -## Changelog since v2.0.1 +### Changelog since v2.0.1 #### New - [New] Initialize bkrepo to modify httpSchema [link](http://github.com/TencentBlueKing/bk-ci/issues/10056) # v2.0.1 ## 2024-03-01 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### Fixes - [Fix] Fix the problem of new project failure [link](http://github.com/TencentBlueKing/bk-ci/issues/10045) # v2.0.0 ## 2024-01-02 -## Changelog since v1.14.0 +### Changelog since v1.14.0 #### New - [New] Support the authority center RBAC [link](http://github.com/TencentBlueKing/bk-ci/issues/7794) - [New] Add project management front-end service [link](http://github.com/TencentBlueKing/bk-ci/issues/7923) diff --git a/CHANGELOG/en/CHANGELOG-2.1.md b/CHANGELOG/en/CHANGELOG-2.1.md index b45b5f1f7891..0d668bc7d5f2 100644 --- a/CHANGELOG/en/CHANGELOG-2.1.md +++ b/CHANGELOG/en/CHANGELOG-2.1.md @@ -36,7 +36,7 @@ # v2.1.3 ## 2024-05-28 -## Changelog since v2.1.2 +### Changelog since v2.1.2 #### New - [New] Modification of the login failure pop-up window specification [link](http://github.com/TencentBlueKing/bk-ci/issues/8125) @@ -45,19 +45,19 @@ # v2.1.2 ## 2024-05-20 -## Changelog since v2.1.1 +### Changelog since v2.1.1 #### Fixes - [Fix] [Community] Listing failure & white screen issue on pipeline execution page [v2.1.0+] [Link](http://github.com/TencentBlueKing/bk-ci/issues/10357) # v2.1.1 ## 2024-04-26 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### Fixes - [Fix] Failed to start the process service in version 2.1 [link](http://github.com/TencentBlueKing/bk-ci/issues/10271) # v2.1.0 ## 2024-04-22 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### New - [New] Docker build machine supports extended resource scheduling [link](http://github.com/TencentBlueKing/bk-ci/issues/10162) - [New] Link between operational products and organizational structure [link](http://github.com/TencentBlueKing/bk-ci/issues/10213) @@ -331,7 +331,7 @@ # v2.1.0-rc.6 ## 2024-04-19 -## Changelog since v2.1.0-rc.5 +### Changelog since v2.1.0-rc.5 #### New - [New] Docker build machine supports extended resource scheduling [link](http://github.com/TencentBlueKing/bk-ci/issues/10162) - [New] Link between operational products and organizational structure [link](http://github.com/TencentBlueKing/bk-ci/issues/10213) @@ -358,7 +358,7 @@ # v2.1.0-rc.5 ## 2024-04-10 -## Changelog since v2.1.0-rc.4 +### Changelog since v2.1.0-rc.4 #### New - [New] Create/edit project openapi and add mandatory check for operational products [link](http://github.com/TencentBlueKing/bk-ci/issues/10088) - [New] Support customizing project-level user groups through the interface [link](http://github.com/TencentBlueKing/bk-ci/issues/10025) @@ -390,7 +390,7 @@ # v2.1.0-rc.4 ## 2024-03-22 -## Changelog since v2.1.0-rc.3 +### Changelog since v2.1.0-rc.3 #### New - [New] Public build machine supports persistent build container scheduling [link](http://github.com/TencentBlueKing/bk-ci/issues/9269) - [New] Enable or disable broadcast notifications for the project [link](http://github.com/TencentBlueKing/bk-ci/issues/10080) @@ -415,7 +415,7 @@ # v2.1.0-rc.3 ## 2024-03-07 -## Changelog since v2.1.0-rc.2 +### Changelog since v2.1.0-rc.2 #### New - [New] SVN webhook interface switch [link](http://github.com/TencentBlueKing/bk-ci/issues/9302) - [New] Pipeline build history table, drag table column width memory function [link](http://github.com/TencentBlueKing/bk-ci/issues/10065) @@ -450,7 +450,7 @@ # v2.1.0-rc.2 ## 2024-02-22 -## Changelog since v2.1.0-rc.1 +### Changelog since v2.1.0-rc.1 #### New - [New] Migration logic optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10014) - [New] User group member addition optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/9909) @@ -478,7 +478,7 @@ # v2.1.0-rc.1 ## 2024-01-16 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### New - [New] Pipeline archive [link](http://github.com/TencentBlueKing/bk-ci/issues/9397) - [New] Support adding pipeline permissions to a group of users [link](http://github.com/TencentBlueKing/bk-ci/issues/9690) diff --git a/CHANGELOG/en/CHANGELOG-3.0.md b/CHANGELOG/en/CHANGELOG-3.0.md index 077bfeb3e5f6..3ed69c14208a 100644 --- a/CHANGELOG/en/CHANGELOG-3.0.md +++ b/CHANGELOG/en/CHANGELOG-3.0.md @@ -11,7 +11,7 @@ # v3.0.0 ## 2024-09-10 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### New ##### Pipeline - Pipeline as code @@ -227,7 +227,7 @@ # v3.0.0-rc.1 ## 2024-09-10 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### New ##### Pipeline - Pipeline as code diff --git a/CHANGELOG/en/CHANGELOG-3.1.md b/CHANGELOG/en/CHANGELOG-3.1.md new file mode 100644 index 000000000000..2277cf869d23 --- /dev/null +++ b/CHANGELOG/en/CHANGELOG-3.1.md @@ -0,0 +1,223 @@ + +- [v3.1.0-rc.3](#v310-rc3) + - [Changelog since v3.1.0-rc.2](#changelog-since-v310-rc2) + +- [v3.1.0-rc.2](#v310-rc2) + - [Changelog since v3.1.0-rc.1](#changelog-since-v310-rc1) + +- [v3.1.0-rc.1](#v310-rc1) + - [Changelog since v3.0.0](#changelog-since-v300) + + + + + + +# v3.1.0-rc.3 +## 2024-11-22 +### Changelog since v3.1.0-rc.2 +#### New + +##### Pipeline +- [New] feat: Pipeline variable syntax supports two styles [link](http://github.com/TencentBlueKing/bk-ci/issues/10576) +- [New] [bugfix] okhttp3 Response not closing actively may cause potential memory leak [link](http://github.com/TencentBlueKing/bk-ci/issues/11234) +- [New] [Blue Shield - Reviewed by the Review Committee] [PAC] feat: Create/edit pipelines to support arranging pipelines in Code [link](http://github.com/TencentBlueKing/bk-ci/issues/8125) +- [New] feat: Optimize the editing of drop-down type variable options [link](http://github.com/TencentBlueKing/bk-ci/issues/10747) +- [New] feat: Remove CI administrator related information from pipeline group management [link](http://github.com/TencentBlueKing/bk-ci/issues/11165) +- [New] Pipeline plugin development custom UI hopes to get the jobid attribute of the container [link](http://github.com/TencentBlueKing/bk-ci/issues/11197) +- [New] feat: When the pipeline runs concurrently, support limiting the number of concurrent connections and queuing [link](http://github.com/TencentBlueKing/bk-ci/issues/10718) +- [New] feat: Recommended version number optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10958) +- [New] feat: Pipeline trigger history supports searching by trigger results [link](http://github.com/TencentBlueKing/bk-ci/issues/11006) +- [New] Added a build details view configuration item. When you click a plugin in the build details interface, the default page you enter is the log or configuration tab page. [Link](http://github.com/TencentBlueKing/bk-ci/issues/10808) +- [New] feat: When queuing in a mutually exclusive job group, the queue length supports up to 50 [link](http://github.com/TencentBlueKing/bk-ci/issues/10975) + +##### Store +- [New] feat:java plugin supports running in a specified java environment [link](http://github.com/TencentBlueKing/bk-ci/issues/10978) + +##### Environmental Management +- [New] feat: Environment management optimization changes [link](http://github.com/TencentBlueKing/bk-ci/issues/11003) + +##### Log Service +- [New] When accessing the pipeline search log, the search bar above will disappear when querying the search log in the full-screen browser [link](http://github.com/TencentBlueKing/bk-ci/issues/11118) + +###### Permission Center +- [New] feat: Optimize the acquisition of user group members under the project [link](http://github.com/TencentBlueKing/bk-ci/issues/11221) +- [New] feat: Optimize user application to join group [link](http://github.com/TencentBlueKing/bk-ci/issues/11219) +- [New] feat: Environment supports resource-level permission management entry [link](http://github.com/TencentBlueKing/bk-ci/issues/11074) +- [New] feat: pipeline list display permission control [link](http://github.com/TencentBlueKing/bk-ci/issues/10895) +- [New] feat: Provides interfaces for obtaining the user groups and renewal of users [link](http://github.com/TencentBlueKing/bk-ci/issues/11136) +- [New] feat: auth service open class interface rectification [link](http://github.com/TencentBlueKing/bk-ci/issues/10403) +- [New] bug: Changes in the returned fields of the query department information interface cause exceptions [link](http://github.com/TencentBlueKing/bk-ci/issues/11151) +- [New] feat: Optimize the synchronization logic of user groups [link](http://github.com/TencentBlueKing/bk-ci/issues/11122) + +##### project management +- [New] feat: Fix the invalid change of maximum authorized scope [link](http://github.com/TencentBlueKing/bk-ci/issues/11153) + +##### Dispatch +- [New] feat: Third-party build machines support running build tasks using dcoker [link](http://github.com/TencentBlueKing/bk-ci/issues/9820) +- [New] feat: No compilation resource optimization environment dependency Dispatch [link](http://github.com/TencentBlueKing/bk-ci/issues/11126) +- [New] feat: The builder triggers the user to be adjusted to the pipeline authority holder [link](http://github.com/TencentBlueKing/bk-ci/issues/11117) + +##### Agent +- [New] feat: pipeline/job concurrency and queue data landing [ link ](http://github.com/TencentBlueKing/bk-ci/issues/10997) +- [New] [bugfix] The bash plugin unconfigures and there is dirty data [link](http://github.com/TencentBlueKing/bk-ci/issues/11177) + +##### Uncategorized +- [New] feat: Fix the startup problem of the open source version [link](http://github.com/TencentBlueKing/bk-ci/issues/11202) +- [New] feat: Added Hongmeng platform [link](http://github.com/TencentBlueKing/bk-ci/issues/11191) +- [New] feat: Adjust the helm image to support imageRegistry configuration [link](http://github.com/TencentBlueKing/bk-ci/issues/11171) +- [New] [feat] API documentation optimization-2024-10 batch [link](http://github.com/TencentBlueKing/bk-ci/issues/11107) +- [New] feat: Interaction optimization when dependent services are not deployed [link](http://github.com/TencentBlueKing/bk-ci/issues/10612) +- [New] SQL doc document update [link](http://github.com/TencentBlueKing/bk-ci/issues/9974) +- [New] feat: Support viewing version logs [link](http://github.com/TencentBlueKing/bk-ci/issues/10938) +- [New] feat: Changes in Blue Whale 7.2 version [link](http://github.com/TencentBlueKing/bk-ci/issues/10558) +- [New] Unify the style and content of the top bar drop-down box of the product [link](http://github.com/TencentBlueKing/bk-ci/issues/10939) + +#### optimization + +##### Pipeline +- [Optimization] pref : The file operator related to the pipeline is adjusted to the authority holder of the pipeline [link](http://github.com/TencentBlueKing/bk-ci/issues/11016) + +##### Store +- [Optimization] perf: Optimize the default plugin query in the plugin management menu [link](http://github.com/TencentBlueKing/bk-ci/issues/11142) +- [Optimization] pref : Adjust the file storage path of the file upload interface [link](http://github.com/TencentBlueKing/bk-ci/issues/10919) + +##### Uncategorized +- [Optimization] perf: The version log date is adjusted to the second-level title [link](http://github.com/TencentBlueKing/bk-ci/issues/11162) + +#### Fixes + +##### Pipeline +- [Fix] Bug: When clicking Skip during finally stage execution, the failed job status will be stuck in the execution [link](http://github.com/TencentBlueKing/bk-ci/issues/11143) +- [Fix] feat: Template management-list supports display fields and sorting optimization [ link ](http://github.com/TencentBlueKing/bk-ci/issues/11056) +- [Fix] bug: fix github pull request id out of bounds [link](http://github.com/TencentBlueKing/bk-ci/issues/11146) + +##### Code Repository +- [Fix] bug: When PAC is enabled in the fixed code base, the git_project_id field is empty [link](http://github.com/TencentBlueKing/bk-ci/issues/11167) + +##### Store +- [Fix] Bug: When different language plugins generate startup commands for the same job, there may be conflicts due to system variables [link](http://github.com/TencentBlueKing/bk-ci/issues/11229) +- [Fix] bug: Optimize the log permissions for viewing store components [link](http://github.com/TencentBlueKing/bk-ci/issues/11208) +- [Fix] Bug: Optimize the paging data query of the plugin installed in the project, and exclude the built-in pipeline associated with the plugin [link](http://github.com/TencentBlueKing/bk-ci/issues/11210) + +##### Quality Red Line +- [Fix] Bug: When using pipeline variables to pass in multiple reviewers, the approval does not take effect [link](http://github.com/TencentBlueKing/bk-ci/issues/11127) + +###### Permission Center +- [Fix] Synchronize data when adding personnel to permission management user group template [link](http://github.com/TencentBlueKing/bk-ci/issues/11217) + +##### Uncategorized +- [Fix] bug: Remove illegal placeholder information in international description information [link](http://github.com/TencentBlueKing/bk-ci/issues/11182) +- [Fix] fix UnreachableCode [link](http://github.com/TencentBlueKing/bk-ci/issues/11172) +- [Fix] Bug: Optimize cluster name obtained based on Profile [link](http://github.com/TencentBlueKing/bk-ci/issues/11137) +# v3.1.0-rc.2 +## 2024-10-26 +## Changelog since v3.1.0-rc.1 +#### New +##### Pipeline +- [New] Recommended version number optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10958) +- [New] Support pipeline indicator monitoring [link](http://github.com/TencentBlueKing/bk-ci/issues/9860) +- [New] Added label display to pipeline list [link](http://github.com/TencentBlueKing/bk-ci/issues/11054) +- [New] Template management - list supports display fields and sorting optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/11056) +- [New] Source material display optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10733) +- [New] Support obtaining parent task ID in Post action [link](http://github.com/TencentBlueKing/bk-ci/issues/10968) +- [New] Stage review supports checklist confirmation scenarios [link](http://github.com/TencentBlueKing/bk-ci/issues/10920) +- [New] AI big model integration [link](http://github.com/TencentBlueKing/bk-ci/issues/10825) +- [New] Enrich the audit function of pipeline-stage admission, support configuring roles or user groups as auditors [link](http://github.com/TencentBlueKing/bk-ci/issues/10689) +- [New] Pipeline log supports AI repair [link](http://github.com/TencentBlueKing/bk-ci/issues/10913) +- [New] When the pipeline runs concurrently, support limiting the number of concurrent connections and queuing [link](http://github.com/TencentBlueKing/bk-ci/issues/10718) +- [New] Added default public plugin display in plugin list in plugin management menu [link](http://github.com/TencentBlueKing/bk-ci/issues/10472) +- [New] When the policy is "Lock Build Number", the execution interface can modify the current value [link](http://github.com/TencentBlueKing/bk-ci/issues/11089) +- [New] Community version pipeline completion notification, support notification group [link](http://github.com/TencentBlueKing/bk-ci/issues/10976) +- [New] When queuing in a mutually exclusive job group, the queue length supports up to 50 [link](http://github.com/TencentBlueKing/bk-ci/issues/10975) +- [New] Trigger event replay operation permission control [link](http://github.com/TencentBlueKing/bk-ci/issues/11052) +- [New] Support retries for executions triggered by sub-pipeline calls [link](http://github.com/TencentBlueKing/bk-ci/issues/11015) +- [New] "Execute when all parameters meet the conditions" and "Do not execute when all parameters meet the conditions" in UI mode are converted to Code optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10930) +- [New] MR event trigger support WIP [link](http://github.com/TencentBlueKing/bk-ci/issues/10683) +- [New] Worker Bee MR trigger adds action=edit [link](http://github.com/TencentBlueKing/bk-ci/issues/11024) +##### Code Repository +- [New] Added access to worker bees and github oauth url build interface [link](http://github.com/TencentBlueKing/bk-ci/issues/10826) +###### Permission Center +- [New] Synchronize and store resource group permission data in separate tables [link](http://github.com/TencentBlueKing/bk-ci/issues/10964) +- [New] Create custom groups and grant group permissions [link](http://github.com/TencentBlueKing/bk-ci/issues/11026) +##### Environmental Management +- [New] Third-party build machines support running build tasks using dcoker [link](http://github.com/TencentBlueKing/bk-ci/issues/9820) +- [New] Support for third-party build machine DockerUi interface [link](http://github.com/TencentBlueKing/bk-ci/issues/10962) +##### Openapi +- [New] OpenApi provides an interface for forwarding Turbo compilation to accelerate reporting of resource statistics data [link](http://github.com/TencentBlueKing/bk-ci/issues/10508) +##### other +- [New] Support viewing version logs [link](http://github.com/TencentBlueKing/bk-ci/issues/10938) +- [New] Optimize AESUtil [link](http://github.com/TencentBlueKing/bk-ci/issues/11084) +- [New] SQL doc document update [link](http://github.com/TencentBlueKing/bk-ci/issues/9974) +- [New] Engine and other MQ scenarios are connected to the SCS framework [link](http://github.com/TencentBlueKing/bk-ci/issues/7443) + +#### optimization +##### Store +- [Optimization] Optimization of store component indicator data fields [link](http://github.com/TencentBlueKing/bk-ci/issues/10219) +##### Stream +- [Optimization] [stream] Optimization of retention issues [link](http://github.com/TencentBlueKing/bk-ci/issues/11045) + +#### Fixes +##### Pipeline +- [Fix] The CONTAINER_ID field value inserted into the T_PIPELINE_BUILD_RECORD_TASK table in some build scenarios is incorrect [link](http://github.com/TencentBlueKing/bk-ci/issues/11029) +- [Fix] The trigger condition introduces ${{ variables.xxx }} variables and cannot trigger the pipeline [link](http://github.com/TencentBlueKing/bk-ci/issues/10987) +- [Fix] Trigger variable supplement [link](http://github.com/TencentBlueKing/bk-ci/issues/11002) +##### Store +- [Fix] Optimize the upload and download of store component package files [link](http://github.com/TencentBlueKing/bk-ci/issues/11115) +- [Fix] When updating component associated initialization project information, the old debug project records were not successfully cleared when adding debug project records [link](http://github.com/TencentBlueKing/bk-ci/issues/11011) +##### Product Library +- [Fix] Archive report plugin creation token is not implemented [link](http://github.com/TencentBlueKing/bk-ci/issues/10693) + + +# v3.1.0-rc.1 +## 2024-10-15 +## Changelog since v3.0.0 +#### New +##### Pipeline +- [New] Optimize the display of names in the breadcrumbs of pipeline view page/edit page/build details interface [link](http://github.com/TencentBlueKing/bk-ci/issues/10800) +- [New] Pipeline version management mechanism [link](http://github.com/TencentBlueKing/bk-ci/issues/8161) +- [New] Job concurrency supports Code configuration [link](http://github.com/TencentBlueKing/bk-ci/issues/10860) +- [New] Draft version UI display [link](http://github.com/TencentBlueKing/bk-ci/issues/9861) +##### Store +- [New] Whether the SDK- related API is displayed in the application list and supports configurability [ link ](http://github.com/TencentBlueKing/bk-ci/issues/10840) +###### Permission Center +- [New] Project member management optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10927) +- [New] Active user record operations and times [link](http://github.com/TencentBlueKing/bk-ci/issues/10891) +- [New] Support searching project members by expiration date/user group name [link](http://github.com/TencentBlueKing/bk-ci/issues/10892) +- [New] Issue: Fix the problem of querying the entire project table under sample authentication [link](http://github.com/TencentBlueKing/bk-ci/issues/10941) +- [New] oauth2 adds password mode [link](http://github.com/TencentBlueKing/bk-ci/issues/10663) +##### Stream +- [New] [stream] Optimize the triggering time of large warehouse [link](http://github.com/TencentBlueKing/bk-ci/issues/10861) +- [New] Notification method for stream stage review supports enterprise WeChat groups [link](http://github.com/TencentBlueKing/bk-ci/issues/10796) +##### Dispatch +- [New] Support Code configuration for reusing build environment between jobs of third-party build machines [link](http://github.com/TencentBlueKing/bk-ci/issues/10254) +- [New] Optimize resource Dispatch priority when building the same pipeline multiple times [link](http://github.com/TencentBlueKing/bk-ci/issues/9897) +- [New] Remove the config ns configuration of the docker build plugin [link](http://github.com/TencentBlueKing/bk-ci/issues/10926) +- [New] AgentId reuse type conversion issue [link](http://github.com/TencentBlueKing/bk-ci/issues/10915) +##### Worker +- [New] Support worker running in JDK17 [link](http://github.com/TencentBlueKing/bk-ci/issues/10412) +##### other +- [New] Newly started POD needs to be warmed up [link](http://github.com/TencentBlueKing/bk-ci/issues/10887) +- [New] OpenAPI filter optimization [link](http://github.com/TencentBlueKing/bk-ci/issues/10679) +- [New] update lerna +yarn workspace to pnpm [link](http://github.com/TencentBlueKing/bk-ci/issues/8125) + +#### optimization +##### Pipeline +- [Optimization] When running a pipeline with a matrix, the task before the matrix split does not need to be written into the record table [link](http://github.com/TencentBlueKing/bk-ci/issues/10873) +##### Store +Optimize the error message when pulling the plugin task.json file content [link](http://github.com/TencentBlueKing/bk-ci/issues/10446) +- [Optimization] Optimization of signature verification of sensitive interfaces in stores [link](http://github.com/TencentBlueKing/bk-ci/issues/10759) +- [Optimization] Change the application schema so that each version can have different configurations [link](http://github.com/TencentBlueKing/bk-ci/issues/10929) +- [Optimization] Optimization of the public build machine plugin cache path and variable adjustment [link](http://github.com/TencentBlueKing/bk-ci/issues/10844) + +#### Fixes +##### Pipeline +- [Fix] Prompt to save when switching to Code mode on non-editing pages [link](http://github.com/TencentBlueKing/bk-ci/issues/10933) +- [Fix] Failed retry display issue on new build details page [link](http://github.com/TencentBlueKing/bk-ci/issues/10735) +##### Store +- [Fix] The pipeline template was uploaded to the store, but it could not be found in the "store" tab when creating a new pipeline [link](http://github.com/TencentBlueKing/bk-ci/issues/10865) +- [Fix] The plugin environment information query interface does not correctly handle the plugin test branch version number [link](http://github.com/TencentBlueKing/bk-ci/issues/10924) +###### Permission Center +- [Fix] bk-permission project member management [link](http://github.com/TencentBlueKing/bk-ci/issues/9620) +##### Stream +- [Fix] Fixed the regular expression error of stream new environment name [link](http://github.com/TencentBlueKing/bk-ci/issues/10939) diff --git a/CHANGELOG/genBundledVersionLog.py b/CHANGELOG/genBundledVersionLog.py index 3d887ff5523c..4e20e5fd8f09 100644 --- a/CHANGELOG/genBundledVersionLog.py +++ b/CHANGELOG/genBundledVersionLog.py @@ -6,6 +6,7 @@ import sys VERSION_LOG_PATH = os.environ.get("VERSION_LOG_PATH", os.getcwd()) +VERSION_LOG_DIALOG_VISIBLE = os.environ.get("VERSION_LOG_DIALOG_VISIBLE", 'True').lower() in ('true', '1') # data元素格式 ''' @@ -21,7 +22,8 @@ "code": 0, "errorMsg": None, "data": [], - "requestId": None + "requestId": None, + "dialogVisible": VERSION_LOG_DIALOG_VISIBLE } DEFAULT_LANGUAGE = "zh_CN" time_pattern = r'\d{4}-\d{2}-\d{2}' diff --git a/CHANGELOG/zh_CN/CHANGELOG-2.0.md b/CHANGELOG/zh_CN/CHANGELOG-2.0.md index fd7890931d90..948dc174a495 100644 --- a/CHANGELOG/zh_CN/CHANGELOG-2.0.md +++ b/CHANGELOG/zh_CN/CHANGELOG-2.0.md @@ -36,7 +36,7 @@ # v2.0.3 ## 2024-05-28 -## Changelog since v2.0.2 +### Changelog since v2.0.2 #### 新增 - [新增] 登录失效弹窗规范修改 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8125) @@ -46,19 +46,19 @@ # v2.0.2 ## 2024-03-04 -## Changelog since v2.0.1 +### Changelog since v2.0.1 #### 新增 - [新增] 初始化bkrepo可以修改httpSchema [链接](http://github.com/TencentBlueKing/bk-ci/issues/10056) # v2.0.1 ## 2024-03-01 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### 修复 - [修复] 修复新建项目失败 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10045) # v2.0.0 ## 2024-01-02 -## Changelog since v1.14.0 +### Changelog since v1.14.0 #### 新增 - [新增] 蓝盾对接权限中心RBAC [链接](http://github.com/TencentBlueKing/bk-ci/issues/7794) - [新增] 添加项目管理前端服务 [链接](http://github.com/TencentBlueKing/bk-ci/issues/7923) diff --git a/CHANGELOG/zh_CN/CHANGELOG-2.1.md b/CHANGELOG/zh_CN/CHANGELOG-2.1.md index b15d57d8f172..3b55ef3c7c4f 100644 --- a/CHANGELOG/zh_CN/CHANGELOG-2.1.md +++ b/CHANGELOG/zh_CN/CHANGELOG-2.1.md @@ -45,7 +45,7 @@ # v2.1.3 ## 2024-05-28 -## Changelog since v2.1.2 +### Changelog since v2.1.2 #### 新增 - [新增] 登录失效弹窗规范修改 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8125) @@ -54,19 +54,19 @@ # v2.1.2 ## 2024-05-20 -## Changelog since v2.1.1 +### Changelog since v2.1.1 #### 修复 - [修复] [社区]上架失败&流水线执行页面白屏问题[v2.1.0+] [链接](http://github.com/TencentBlueKing/bk-ci/issues/10357) # v2.1.1 ## 2024-04-26 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### 修复 - [修复] 2.1版本process服务启动失败 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10271) # v2.1.0 ## 2024-04-22 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### 新增 - [新增] Docker构建机支持拓展资源调度 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10162) - [新增] 运营产品和组织架构联动 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10213) @@ -337,9 +337,10 @@ - [修复] openapi 判断是否项目成员没有根据项目路由 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9427) - [修复] devcloud类型登录调试,窗口大小无法自适应 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9418) - [修复] 共享凭据不需要依赖插件敏感接口权限校验 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9398) + # v2.1.0-rc.6 ## 2024-04-19 -## Changelog since v2.1.0-rc.5 +### Changelog since v2.1.0-rc.5 #### 新增 - [新增] Docker构建机支持拓展资源调度 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10162) - [新增] 运营产品和组织架构联动 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10213) @@ -366,7 +367,7 @@ # v2.1.0-rc.5 ## 2024-04-10 -## Changelog since v2.1.0-rc.4 +### Changelog since v2.1.0-rc.4 #### 新增 - [新增] 创建/编辑项目openapi增加运营产品必填检查 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10088) - [新增] 支持通过接口自定义项目级别用户组 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10025) @@ -398,7 +399,7 @@ # v2.1.0-rc.4 ## 2024-03-22 -## Changelog since v2.1.0-rc.3 +### Changelog since v2.1.0-rc.3 #### 新增 - [新增] 公共构建机支持持久化构建容器调度 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9269) - [新增] 项目启用停用广播通知 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10080) @@ -423,7 +424,7 @@ # v2.1.0-rc.3 ## 2024-03-07 -## Changelog since v2.1.0-rc.2 +### Changelog since v2.1.0-rc.2 #### 新增 - [新增] svn webhook接口切换 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9302) - [新增] 流水线构建历史表格,拖拽表格列宽记忆功能 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10065) @@ -458,7 +459,7 @@ # v2.1.0-rc.2 ## 2024-02-22 -## Changelog since v2.1.0-rc.1 +### Changelog since v2.1.0-rc.1 #### 新增 - [新增] 迁移逻辑优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10014) - [新增] 用户组添加成员优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9909) @@ -486,7 +487,7 @@ # v2.1.0-rc.1 ## 2024-01-16 -## Changelog since v2.0.0 +### Changelog since v2.0.0 #### 新增 - [新增] 流水线归档 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9397) - [新增] 支持给一组用户添加流水线权限 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9690) diff --git a/CHANGELOG/zh_CN/CHANGELOG-3.0.md b/CHANGELOG/zh_CN/CHANGELOG-3.0.md index 652fd5d6acb7..dbfae1cbd9c9 100644 --- a/CHANGELOG/zh_CN/CHANGELOG-3.0.md +++ b/CHANGELOG/zh_CN/CHANGELOG-3.0.md @@ -1,4 +1,8 @@ +- [v3.0.11](#v3011) + - [Changelog since v3.0.0](#changelog-since-v300) +- [v3.0.1-v3.0.11] + - 因镜像版本与仓库版本没有统一,v3.0.1-v3.0.11已有镜像版本,但没有仓库版本,所以仓库这些版本直接跳过 - [v3.0.0](#v300) - [Changelog since v2.1.0](#changelog-since-v210) - [v3.0.0-rc.1](#v300-rc1) @@ -9,9 +13,35 @@ +# v3.0.11 +## 2024-12-05 +### Changelog since v3.0.0 +#### 新增 + +##### 未分类 +- [新增] feat: 调整helm的镜像使其支持配置imageRegistry [链接](http://github.com/TencentBlueKing/bk-ci/issues/11171) +- [新增] feat:依赖的服务未部署时的交互优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10612) +- [新增] feat:支持查看版本日志 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10938) +- [新增] feat: 蓝鲸7.2版本的改动 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10558) +- [新增] 产品的顶栏下拉框样式和内容统一规范 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10939) +- [新增] feat: 把docker build插件的config ns配置给去掉 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10926) +- [新增] feat: 新启动的POD需要热身 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10887) +- [新增] feat: 让worker支持在JDK17中运行 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10412) + +#### 修复 + +##### 流水线 +- [修复] 【蓝盾-评审会已评审】【PAC】feat:新建/编辑流水线支持以 Code 方式编排流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8125) + +##### 权限中心 +- [修复] bug: 权限管理-权限续期数据同步 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11271) + +##### 未分类 +- [修复] fix UnreachableCode [链接](http://github.com/TencentBlueKing/bk-ci/issues/11172) + # v3.0.0 ## 2024-09-10 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### 新增 ##### 流水线 - pipeline as code @@ -227,7 +257,7 @@ # v3.0.0-rc.1 ## 2024-09-10 -## Changelog since v2.1.0 +### Changelog since v2.1.0 #### 新增 ##### 流水线 - pipeline as code diff --git a/CHANGELOG/zh_CN/CHANGELOG-3.1.md b/CHANGELOG/zh_CN/CHANGELOG-3.1.md index 90706f98a554..e57751df795f 100644 --- a/CHANGELOG/zh_CN/CHANGELOG-3.1.md +++ b/CHANGELOG/zh_CN/CHANGELOG-3.1.md @@ -1,4 +1,13 @@ +- [v3.1.0-rc.6](#v310-rc6) + - [Changelog since v3.1.0-rc.5](#changelog-since-v310-rc5) + +- [v3.1.0-rc.5](#v310-rc5) + - [Changelog since v3.1.0-rc.4](#changelog-since-v310-rc4) + +- [v3.1.0-rc.4](#v310-rc4) + - [Changelog since v3.1.0-rc.3](#changelog-since-v310-rc3) + - [v3.1.0-rc.3](#v310-rc3) - [Changelog since v3.1.0-rc.2](#changelog-since-v310-rc2) @@ -13,6 +22,136 @@ +# v3.1.0-rc.6 +## 2025-01-08 +### Changelog since v3.1.0-rc.5 +#### 新增 + +##### 流水线 +- [新增] feat:导出流水线功能优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11304) +- [新增] feat:推荐版本号模版优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11186) +- [新增] feat:Git分支/Tag和Svn分支/Tag类型的变量优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10774) + +##### 权限中心 +- [新增] feat:用户个人视角 权限管理优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11138) + +##### 未分类 +- [新增] feat:支持管理我的 OAUTH [链接](http://github.com/TencentBlueKing/bk-ci/issues/10995) +- [新增] API自动化文档优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11339) +- [新增] feat: 健康检查优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11336) +- [新增] [feat] 插件日志10w+即归档为压缩包 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11183) + +#### 优化 + +##### 流水线 +- [优化] pref:优化流水线项目下已安装插件关联流水线查询 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11307) + +##### 研发商店 +- [优化] pref:研发商店组件内置打包流水线都归属到统一的平台项目下 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10475) + +#### 修复 + +##### 流水线 +- [修复] bug: 修复流水线事件重放报500错误 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11333) +- [修复] bug: GIT触发器单独监听[新增分支]不生效 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11338) + +##### 研发商店 +- [修复] bug:插件执行失败时的错误码类型归属错误问题优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11294) + +# v3.1.0-rc.5 +## 2024-12-23 +### Changelog since v3.1.0-rc.4 +#### 新增 + +##### 流水线 +- [新增] 插件配置支持字段间联动 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11251) +- [新增] feat:Git分支/Tag和Svn分支/Tag类型的变量优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10774) +- [新增] feat: 优化PUSH事件预匹配逻辑 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11317) +- [新增] AI大模型融入 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10825) +- [新增] feat: copilot 编辑器支持免登录 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11290) +- [新增] feat:推荐版本号模版优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11186) +- [新增] 【蓝盾-评审会已评审】【PAC】feat:流水线版本管理机制 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8161) +- [新增] feat:运行时校验权限代持人权限是否已失效 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10478) + +##### 权限中心 +- [新增] feat:提供项目管理相关openapi接口 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11231) + +##### 项目管理 +- [新增] feat:查询项目接口优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11276) + +##### Stream +- [新增] [stream] 项目支持关联到运营产品 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9948) + +##### 调度 +- [新增] feat:优化dispatch-sdk调度逻辑对其他服务的依赖 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10882) + +##### 未分类 +- [新增] feat: 升级openresty到1.19 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11295) +- [新增] openapi新增文档生成能力 [链接](http://github.com/TencentBlueKing/bk-ci/issues/7412) +- [新增] feat: 升级undertow版本解决内存泄漏问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11300) +- [新增] sql doc 文档更新 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9974) + +#### 优化 + +##### 研发商店 +- [优化] pref:nodejs安装包下载地址域名支持按部署环境返回 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11327) +- [优化] pref:研发商店通用化接口封装优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11049) + +##### 环境管理 +- [优化] perf: 增加部分错误码 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11279) + +#### 修复 + +##### 流水线 +- [修复] fix: 执行前暂停的插件弹窗问题处理 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11296) + +##### 研发商店 +- [修复] bug:插件最新版本使用历史版本修复方式发布后,再用普通方式发布的分支会继承上一次的 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11301) + +##### 未分类 +- [修复] bugfix: 升级JDK17导致worker无法强杀进程 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11320) + +# v3.1.0-rc.4 +## 2024-12-05 +### Changelog since v3.1.0-rc.3 +#### 新增 + +##### 流水线 +- [新增] feat:项目设置支持管理员配置项目下流水线的命名规范 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11057) +- [新增] feat:创建流水线时支持设置标签 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11055) +- [新增] 流水线插件开发自定义UI希望可以获取到container 的 jobid 属性 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11197) +- [新增] feat: 触发器的自定义触发控制回调增加事件类型 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11196) + +##### 未分类 +- [新增] feat: 整理网关的tag路由 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11050) +- [新增] feat:我的凭证列表展示创建和更新信息 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11023) +- [新增] worker和agent支持java17和java8同步运行 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10586) +- [新增] feat: 引擎等MQ场景接入SCS框架 [链接](http://github.com/TencentBlueKing/bk-ci/issues/7443) + +#### 优化 + +##### 代码库 +- [优化] perf: repository服务去掉对git命令依赖 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11193) + +#### 修复 + +##### 流水线 +- [修复] bug: 流水线另存为模版,模版名字与流水线名字一样,会报"流水线名称已被使用" [链接](http://github.com/TencentBlueKing/bk-ci/issues/11264) +- [修复] bug: 创建流水线组失败,导致代码库开启PAC一直显示同步中 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11253) +- [修复] bug: 心跳超时被取消的插件没有刷新前端状态 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11265) +- [修复] bug: [PAC].ci下的目录已经删除,但是关联的流水线组没有删除,也无法手工删除 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11254) + +##### 权限中心 +- [修复] bug: 权限管理-权限续期数据同步 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11271) + +##### 调度 +- [修复] feat:第三方构建机支持使用 dcoker 运行构建任务 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9820) + +##### 未分类 +- [修复] bugfix: Agent没有开启监控会无限打日志 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11274) +- [修复] feat:支持查看版本日志 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10938) +- [修复] bug: 版本日志根据配置控制弹框 [链接](http://github.com/TencentBlueKing/bk-ci/issues/11260) + # v3.1.0-rc.3 ## 2024-11-22 ### Changelog since v3.1.0-rc.2 diff --git a/docker-images/core/ci/dockerfile/gateway.Dockerfile b/docker-images/core/ci/dockerfile/gateway.Dockerfile index 0bc3b83a2d2d..db05caa11939 100644 --- a/docker-images/core/ci/dockerfile/gateway.Dockerfile +++ b/docker-images/core/ci/dockerfile/gateway.Dockerfile @@ -1,4 +1,4 @@ -FROM bkci/openresty:0.0.1 +FROM bkci/openresty:0.0.2 LABEL maintainer="Tencent BlueKing Devops" diff --git a/docker-images/core/ci/dockerfile/openresty.Dockerfile b/docker-images/core/ci/dockerfile/openresty.Dockerfile index e6d281614bfc..f69815384a8c 100644 --- a/docker-images/core/ci/dockerfile/openresty.Dockerfile +++ b/docker-images/core/ci/dockerfile/openresty.Dockerfile @@ -8,15 +8,15 @@ ENV INSTALL_PATH="/data/tmp/installfiles" RUN mkdir -p ${INSTALL_PATH} &&\ wget https://openresty.org/package/centos/7/x86_64/openresty-zlib-1.2.11-3.el7.centos.x86_64.rpm -P ${INSTALL_PATH} &&\ wget https://openresty.org/package/centos/7/x86_64/openresty-pcre-8.44-1.el7.x86_64.rpm -P ${INSTALL_PATH} &&\ - wget https://openresty.org/package/centos/7/x86_64/openresty-openssl111-1.1.1g-3.el7.x86_64.rpm -P ${INSTALL_PATH} &&\ - wget https://openresty.org/package/centos/7/x86_64/openresty-1.17.8.2-1.el7.x86_64.rpm -P ${INSTALL_PATH} + wget https://openresty.org/package/centos/7/x86_64/openresty-openssl111-1.1.1w-1.el7.x86_64.rpm -P ${INSTALL_PATH} &&\ + wget https://openresty.org/package/centos/7/x86_64/openresty-1.19.9.1-1.el7.x86_64.rpm -P ${INSTALL_PATH} # 安装 RUN chmod 775 ${INSTALL_PATH}/*.rpm &&\ rpm -ivh ${INSTALL_PATH}/openresty-zlib-1.2.11-3.el7.centos.x86_64.rpm --replacefiles &&\ rpm -ivh ${INSTALL_PATH}/openresty-pcre-8.44-1.el7.x86_64.rpm --replacefiles &&\ - rpm -ivh ${INSTALL_PATH}/openresty-openssl111-1.1.1g-3.el7.x86_64.rpm --replacefiles &&\ - rpm -ivh ${INSTALL_PATH}/openresty-1.17.8.2-1.el7.x86_64.rpm --replacefiles + rpm -ivh ${INSTALL_PATH}/openresty-openssl111-1.1.1w-1.el7.x86_64.rpm --replacefiles &&\ + rpm -ivh ${INSTALL_PATH}/openresty-1.19.9.1-1.el7.x86_64.rpm --replacefiles # 删除文件 RUN rm -rf ${INSTALL_PATH} diff --git a/docs/overview/db/devops_ci_artifactory.md b/docs/overview/db/devops_ci_artifactory.md index b13839df9a43..84cee4638599 100644 --- a/docs/overview/db/devops_ci_artifactory.md +++ b/docs/overview/db/devops_ci_artifactory.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_artifactory -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_artifactory 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_auth.md b/docs/overview/db/devops_ci_auth.md index 246058ced555..698b5c1c48d3 100644 --- a/docs/overview/db/devops_ci_auth.md +++ b/docs/overview/db/devops_ci_auth.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_auth -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_auth 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_dispatch.md b/docs/overview/db/devops_ci_dispatch.md index 45a7d4dd2a61..ecb64c321f0a 100644 --- a/docs/overview/db/devops_ci_dispatch.md +++ b/docs/overview/db/devops_ci_dispatch.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_dispatch -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_dispatch 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_environment.md b/docs/overview/db/devops_ci_environment.md index ccd99671861d..23f98361bcc9 100644 --- a/docs/overview/db/devops_ci_environment.md +++ b/docs/overview/db/devops_ci_environment.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_environment -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_environment 的数据库文档 | 表名 | 说明 | @@ -264,6 +264,7 @@ | 29 | OS_TYPE | varchar | 64 | 0 | Y | N | | 从 CC 中查到的 os 类型 | | 30 | SERVER_ID | bigint | 20 | 0 | Y | N | | 服务器 id | | 31 | SYSTEM_UPDATE_TIME | timestamp | 19 | 0 | Y | N | | 系统任务更新数据时间 | +| 32 | SIZE | varchar | 32 | 0 | Y | N | | 机型 | **表名:** T_PROJECT_CONFIG diff --git a/docs/overview/db/devops_ci_image.md b/docs/overview/db/devops_ci_image.md index 026cda3dba65..95a248c60c94 100644 --- a/docs/overview/db/devops_ci_image.md +++ b/docs/overview/db/devops_ci_image.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_image -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_image 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_log.md b/docs/overview/db/devops_ci_log.md index 3a8c4f23dfec..8dd7f2310d08 100644 --- a/docs/overview/db/devops_ci_log.md +++ b/docs/overview/db/devops_ci_log.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_log -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_log 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_notify.md b/docs/overview/db/devops_ci_notify.md index 342c4c6aaa86..3d35ee34efb5 100644 --- a/docs/overview/db/devops_ci_notify.md +++ b/docs/overview/db/devops_ci_notify.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_notify -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_notify 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_op.md b/docs/overview/db/devops_ci_op.md index a303de8b68d6..4ceeea2ea058 100644 --- a/docs/overview/db/devops_ci_op.md +++ b/docs/overview/db/devops_ci_op.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_op -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_op 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_openapi.md b/docs/overview/db/devops_ci_openapi.md index 169895172c66..503fa79c6b71 100644 --- a/docs/overview/db/devops_ci_openapi.md +++ b/docs/overview/db/devops_ci_openapi.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_openapi -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_openapi 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_plugin.md b/docs/overview/db/devops_ci_plugin.md index 6500a0ec287d..d43c76b4927f 100644 --- a/docs/overview/db/devops_ci_plugin.md +++ b/docs/overview/db/devops_ci_plugin.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_plugin -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_plugin 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_process.md b/docs/overview/db/devops_ci_process.md index 8f714dcff53d..bd3af8ad3767 100644 --- a/docs/overview/db/devops_ci_process.md +++ b/docs/overview/db/devops_ci_process.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_process -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_process 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_project.md b/docs/overview/db/devops_ci_project.md index a8be36ea8d31..5f71d98ee5b6 100644 --- a/docs/overview/db/devops_ci_project.md +++ b/docs/overview/db/devops_ci_project.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_project -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_project 的数据库文档 | 表名 | 说明 | @@ -270,6 +270,7 @@ | 25 | PROJECT_TYPE | int | 10 | 0 | Y | N | | 项目类型 | | 26 | PRODUCT_ID | int | 10 | 0 | Y | N | | 运营产品 ID | | 27 | PRODUCT_NAME | varchar | 64 | 0 | Y | N | | 运营产品名称 | +| 28 | PROPERTIES | text | 65535 | 0 | Y | N | | 项目其他配置 | **表名:** T_PROJECT_DATA_MIGRATE_HISTORY diff --git a/docs/overview/db/devops_ci_quality.md b/docs/overview/db/devops_ci_quality.md index dc2183cf2899..242ba53efd17 100644 --- a/docs/overview/db/devops_ci_quality.md +++ b/docs/overview/db/devops_ci_quality.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_quality -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_quality 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_repository.md b/docs/overview/db/devops_ci_repository.md index 0779c6f3556a..881c715a062e 100644 --- a/docs/overview/db/devops_ci_repository.md +++ b/docs/overview/db/devops_ci_repository.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_repository -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_repository 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_sign.md b/docs/overview/db/devops_ci_sign.md index 590d3b62ee57..a23f643cd77a 100644 --- a/docs/overview/db/devops_ci_sign.md +++ b/docs/overview/db/devops_ci_sign.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_sign -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_sign 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_store.md b/docs/overview/db/devops_ci_store.md index d1264a2e0192..6262d50f07d9 100644 --- a/docs/overview/db/devops_ci_store.md +++ b/docs/overview/db/devops_ci_store.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_store -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_store 的数据库文档 | 表名 | 说明 | diff --git a/docs/overview/db/devops_ci_ticket.md b/docs/overview/db/devops_ci_ticket.md index 14c3936daaf1..11a96bdb0b84 100644 --- a/docs/overview/db/devops_ci_ticket.md +++ b/docs/overview/db/devops_ci_ticket.md @@ -2,7 +2,7 @@ **数据库名:** devops_ci_ticket -**文档版本:** 1.0.4 +**文档版本:** 1.0.5 **文档描述:** devops_ci_ticket 的数据库文档 | 表名 | 说明 | diff --git a/src/agent/agent/src/pkg/collector/collector.go b/src/agent/agent/src/pkg/collector/collector.go index dd2e528cae5c..9c8bdf6bb225 100644 --- a/src/agent/agent/src/pkg/collector/collector.go +++ b/src/agent/agent/src/pkg/collector/collector.go @@ -58,6 +58,11 @@ const ( func Collect() { logs.Debug("do Collect") + if config.GAgentConfig.CollectorOn == false { + logs.Info("agent collector off") + return + } + ipChan := config.EBus.Subscribe(config.IpEvent, eBusId, 1) defer func() { @@ -79,11 +84,6 @@ func Collect() { } func doAgentCollect(ctx context.Context) { - if config.GAgentConfig.CollectorOn == false { - logs.Info("agent collector off") - return - } - configContent, err := genTelegrafConfig() if err != nil { logs.WithError(err).Error("genTelegrafConfig error") diff --git a/src/backend/ci/build.gradle.kts b/src/backend/ci/build.gradle.kts index 56d29d828908..9bd7b0f1c94e 100644 --- a/src/backend/ci/build.gradle.kts +++ b/src/backend/ci/build.gradle.kts @@ -148,6 +148,8 @@ allprojects { dependency("org.jvnet.winp:winp:${Versions.Winp}") dependency("net.java.dev.jna:jna:${Versions.Jna}") dependency("org.jenkins-ci:version-number:${Versions.JenkinsVersionNumber}") + // TODO 等undertow升级上来之后可以去掉 + dependency("io.undertow:undertow-core:2.2.37.Final") } } diff --git a/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt b/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt index ff146df41fc1..fa50906ee42e 100644 --- a/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt +++ b/src/backend/ci/buildSrc/src/main/kotlin/constants/Versions.kt @@ -45,7 +45,7 @@ object Versions { const val Resilience4j = "1.7.1" const val jjwt = "0.11.5" const val Okhttp = "4.9.0" - const val jgit = "5.13.1.202206130422-r" + const val jgit = "5.13.3.202401111512-r" const val iam = "1.0.7" const val disklrucache = "2.0.2" const val BkCrypto = "1.1.3" diff --git a/src/backend/ci/core/artifactory/api-artifactory/src/main/kotlin/com/tencent/devops/artifactory/constant/ArtifactoryMessageCode.kt b/src/backend/ci/core/artifactory/api-artifactory/src/main/kotlin/com/tencent/devops/artifactory/constant/ArtifactoryMessageCode.kt index 1af06d9889fd..e9be9d570be6 100644 --- a/src/backend/ci/core/artifactory/api-artifactory/src/main/kotlin/com/tencent/devops/artifactory/constant/ArtifactoryMessageCode.kt +++ b/src/backend/ci/core/artifactory/api-artifactory/src/main/kotlin/com/tencent/devops/artifactory/constant/ArtifactoryMessageCode.kt @@ -90,6 +90,10 @@ object ArtifactoryMessageCode { const val GET_FILE_FAIL = "2102046" // 构建分发获取文件失败 const val JOB_EXECUTE_FAIL = "2102047" // JOB执行失败,msg{0} + const val HANDOVER_TO_PROJECT_DOWNLOAD_PERMISSION_FORBIDDEN = "2102048" // 流水线代持人{0}没有项目{1}下载权限 + // 流水线代持人{0}在项目{1}下没有流水线{2}下载构件权限 + const val HANDOVER_TO_PIPELINE_DOWNLOAD_PERMISSION_FORBIDDEN = "2102049" + const val BK_BLUE_SHIELD_SHARE_FILES_WITH_YOU = "bkBlueShieldShareFilesWithYou" // 【蓝盾版本仓库通知】{0}与你共享{1}文件 // 【蓝盾版本仓库通知】{0}与你共享{1}等{2}个文件 const val BK_BLUE_SHIELD_SHARE_AND_OTHER_FILES_WITH_YOU = "bkBlueShieldShareAndOtherFilesWithYou" diff --git a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgServiceImpl.kt index 91d978cf5fc1..94d6d320159c 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgServiceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgServiceImpl.kt @@ -43,7 +43,7 @@ import com.tencent.devops.common.client.Client import com.tencent.devops.common.service.utils.ZipUtil import com.tencent.devops.store.api.common.ServiceStoreArchiveResource import com.tencent.devops.store.api.common.ServiceStoreResource -import com.tencent.devops.store.pojo.common.CONFIG_JSON_NAME +import com.tencent.devops.store.pojo.common.CONFIG_YML_NAME import com.tencent.devops.store.pojo.common.QueryComponentPkgEnvInfoParam import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StorePkgEnvInfo @@ -101,7 +101,8 @@ abstract class ArchiveStorePkgServiceImpl : ArchiveStorePkgService { val storePkgEnvInfos: List? var packageFileInfos: MutableList? = null try { - handleArchiveFile( + // 解压上传的包 + handlePkgFile( disposition = disposition, inputStream = inputStream, storeType = storeType, @@ -109,20 +110,35 @@ abstract class ArchiveStorePkgServiceImpl : ArchiveStorePkgService { version = version ) val storeArchivePath = buildStoreArchivePath(storeType, storeCode, version) - val bkConfigJsonFile = File(storeArchivePath, CONFIG_JSON_NAME) - storePkgEnvInfos = if (bkConfigJsonFile.exists()) { + val bkConfigFile = File(storeArchivePath, CONFIG_YML_NAME) + storePkgEnvInfos = if (bkConfigFile.exists()) { + // 如果上传的文件是压缩包需要删除原压缩包 + File(storeArchivePath, disposition.fileName).deleteRecursively() client.get(ServiceStoreArchiveResource::class).getComponentPkgEnvInfo( userId = userId, storeType = storeType, storeCode = storeCode, version = version, queryComponentPkgEnvInfoParam = QueryComponentPkgEnvInfoParam( - configFileContent = bkConfigJsonFile.readText() + configFileContent = bkConfigFile.readText() ) ).data } else { - listOf(StorePkgEnvInfo(osName = OSType.WINDOWS.name.lowercase(), defaultFlag = true)) + listOf( + StorePkgEnvInfo( + osName = OSType.WINDOWS.name.lowercase(), + pkgLocalPath = disposition.fileName, + defaultFlag = true + ) + ) } + handleArchiveFile( + storeType = storeType, + storeCode = storeCode, + version = version, + storePkgEnvInfos = storePkgEnvInfos + ) + storePkgEnvInfos?.forEach { storePkgEnvInfo -> var pkgLocalPath = storePkgEnvInfo.pkgLocalPath if (storeType == StoreTypeEnum.ATOM && storePkgEnvInfo.target.isNullOrBlank() && @@ -138,15 +154,23 @@ abstract class ArchiveStorePkgServiceImpl : ArchiveStorePkgService { pkgLocalPath = disposition.fileName } val packageFile = File("$storeArchivePath/$pkgLocalPath") + val packageFileName = packageFile.name val packageFileInfo = PackageFileInfo( - packageFileName = packageFile.name, + packageFileName = packageFileName, packageFilePath = packageFile.absolutePath.removePrefix(getStoreArchiveBasePath()), packageFileSize = packageFile.length(), shaContent = packageFile.inputStream().use { ShaUtils.sha1InputStream(it) } ) - storePkgEnvInfo.pkgRepoPath = "$storeCode/$version/$pkgLocalPath" + val pkgRepoPath = generatePkgRepoPath( + storeCode = storeCode, + version = version, + pkgFileName = packageFileName, + osName = storePkgEnvInfo.osName, + osArch = storePkgEnvInfo.osArch + ) + storePkgEnvInfo.pkgRepoPath = pkgRepoPath storePkgEnvInfo.shaContent = packageFileInfo.shaContent - storePkgEnvInfo.pkgName = packageFileInfo.packageFileName + storePkgEnvInfo.pkgName = packageFileName packageFileInfos!!.add(packageFileInfo) } } finally { @@ -196,7 +220,26 @@ abstract class ArchiveStorePkgServiceImpl : ArchiveStorePkgService { return true } - protected fun handlePkgFile( + protected fun generatePkgRepoPath( + storeCode: String, + version: String, + pkgFileName: String, + osName: String? = null, + osArch: String? = null + ): String { + val pkgRepoPathSb = StringBuilder("$storeCode/$version/") + if (!osName.isNullOrBlank()) { + pkgRepoPathSb.append(osName).append("/") + } + if (!osArch.isNullOrBlank()) { + pkgRepoPathSb.append(osArch).append("/") + } + pkgRepoPathSb.append(pkgFileName) + val pkgRepoPath = pkgRepoPathSb.toString() + return pkgRepoPath + } + + private fun handlePkgFile( disposition: FormDataContentDisposition, inputStream: InputStream, storeType: StoreTypeEnum, @@ -286,11 +329,10 @@ abstract class ArchiveStorePkgServiceImpl : ArchiveStorePkgService { abstract fun getStoreArchiveBasePath(): String abstract fun handleArchiveFile( - disposition: FormDataContentDisposition, - inputStream: InputStream, storeType: StoreTypeEnum, storeCode: String, - version: String + version: String, + storePkgEnvInfos: List? ) override fun getComponentPkgDownloadUrl( diff --git a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToBkRepoServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToBkRepoServiceImpl.kt index c16d9a5fd0c7..b09310e4c8ac 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToBkRepoServiceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToBkRepoServiceImpl.kt @@ -10,13 +10,12 @@ import com.tencent.devops.common.api.constant.STATIC import com.tencent.devops.common.api.exception.RemoteServiceException import com.tencent.devops.common.archive.client.BkRepoClient import com.tencent.devops.common.archive.config.BkRepoClientConfig -import com.tencent.devops.store.pojo.common.CONFIG_JSON_NAME +import com.tencent.devops.store.pojo.common.CONFIG_YML_NAME import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum -import org.glassfish.jersey.media.multipart.FormDataContentDisposition +import com.tencent.devops.store.pojo.common.publication.StorePkgEnvInfo import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import java.io.File -import java.io.InputStream import javax.ws.rs.NotFoundException abstract class ArchiveStorePkgToBkRepoServiceImpl : ArchiveStorePkgServiceImpl() { @@ -34,34 +33,58 @@ abstract class ArchiveStorePkgToBkRepoServiceImpl : ArchiveStorePkgServiceImpl() return System.getProperty("java.io.tmpdir") } - @Suppress("UNCHECKED_CAST") override fun handleArchiveFile( - disposition: FormDataContentDisposition, - inputStream: InputStream, storeType: StoreTypeEnum, storeCode: String, - version: String + version: String, + storePkgEnvInfos: List? ) { - handlePkgFile( - disposition = disposition, - inputStream = inputStream, - storeType = storeType, - storeCode = storeCode, - version = version - ) val storeArchivePath = buildStoreArchivePath(storeType, storeCode, version) - val bkConfigJsonFile = File(storeArchivePath, CONFIG_JSON_NAME) - if (bkConfigJsonFile.exists()) { - // 删除原压缩包 - File(storeArchivePath, disposition.fileName).deleteRecursively() + val prefix = "${getStoreArchiveBasePath()}/${getPkgFileTypeDir(storeType)}" + if (storePkgEnvInfos.isNullOrEmpty()) { + directoryIteration( + directoryFile = File(storeArchivePath), + prefix = prefix, + directoryPath = storeArchivePath, + repoName = getBkRepoName(storeType), + storeType = storeType + ) + } else { + storePkgEnvInfos.forEach { storePkgEnvInfo -> + val pkgLocalPath = storePkgEnvInfo.pkgLocalPath + if (pkgLocalPath.isNullOrBlank()) { + return@forEach + } + val file = File(storeArchivePath, pkgLocalPath) + if (!file.exists()) { + logger.warn("uploadLocalFile file[$pkgLocalPath] not exist!") + return@forEach + } + val pkgRepoPath = generatePkgRepoPath( + storeCode = storeCode, + version = version, + pkgFileName = file.name, + osName = storePkgEnvInfo.osName, + osArch = storePkgEnvInfo.osArch + ) + uploadLocalFile( + storeType = storeType, + repoName = getBkRepoName(storeType), + path = pkgRepoPath, + file = file + ) + } + // 上传配置文件 + val bkConfigFile = File(storeArchivePath, CONFIG_YML_NAME) + if (bkConfigFile.exists()) { + uploadLocalFile( + storeType = storeType, + repoName = getBkRepoName(storeType), + path = bkConfigFile.path.removePrefix(prefix), + file = bkConfigFile + ) + } } - directoryIteration( - directoryFile = File(storeArchivePath), - prefix = "${getStoreArchiveBasePath()}/${getPkgFileTypeDir(storeType)}", - directoryPath = storeArchivePath, - repoName = getBkRepoName(storeType), - storeType = storeType - ) val frontendDir = buildStoreFrontendPath(storeType, storeCode, version) frontendDir?.let { directoryIteration( @@ -93,22 +116,31 @@ abstract class ArchiveStorePkgToBkRepoServiceImpl : ArchiveStorePkgServiceImpl() } else { val path = file.path.removePrefix(prefix) logger.debug("uploadLocalFile fileName=${file.name}|path=$path") - val uploadRepoName = getUploadRepoName(repoName, storeType) - bkRepoClient.uploadLocalFile( - userId = BKREPO_DEFAULT_USER, - projectId = getBkRepoProjectId(storeType), - repoName = uploadRepoName, - path = path, - file = file, - gatewayFlag = false, - bkrepoApiUrl = "${bkRepoClientConfig.bkRepoIdcHost}/api/generic", - userName = bkRepoStoreConfig.bkrepoStoreUserName, - password = bkRepoStoreConfig.bkrepoStorePassword - ) + uploadLocalFile(storeType = storeType, repoName = repoName, path = path, file = file) } } } + private fun uploadLocalFile( + storeType: StoreTypeEnum, + repoName: String, + path: String, + file: File + ) { + val uploadRepoName = getUploadRepoName(repoName, storeType) + bkRepoClient.uploadLocalFile( + userId = BKREPO_DEFAULT_USER, + projectId = getBkRepoProjectId(storeType), + repoName = uploadRepoName, + path = path, + file = file, + gatewayFlag = false, + bkrepoApiUrl = "${bkRepoClientConfig.bkRepoIdcHost}/api/generic", + userName = bkRepoStoreConfig.bkrepoStoreUserName, + password = bkRepoStoreConfig.bkrepoStorePassword + ) + } + private fun getUploadRepoName( repoName: String, storeType: StoreTypeEnum diff --git a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToLocalServiceImpl.kt b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToLocalServiceImpl.kt index 501179abf45d..d1d686f321e3 100644 --- a/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToLocalServiceImpl.kt +++ b/src/backend/ci/core/artifactory/biz-artifactory/src/main/kotlin/com/tencent/devops/artifactory/store/service/impl/ArchiveStorePkgToLocalServiceImpl.kt @@ -33,15 +33,15 @@ import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.service.config.CommonConfig import com.tencent.devops.common.service.utils.HomeHostUtil import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.publication.StorePkgEnvInfo import org.apache.commons.io.FileUtils -import org.glassfish.jersey.media.multipart.FormDataContentDisposition +import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Service import org.springframework.util.FileSystemUtils import java.io.File -import java.io.InputStream import java.net.URLDecoder @Service @@ -54,24 +54,40 @@ class ArchiveStorePkgToLocalServiceImpl : ArchiveStorePkgServiceImpl() { @Autowired lateinit var commonConfig: CommonConfig + companion object { + private val logger = LoggerFactory.getLogger(ArchiveStorePkgToLocalServiceImpl::class.java) + } + override fun getStoreArchiveBasePath(): String { return storeArchiveLocalBasePath } override fun handleArchiveFile( - disposition: FormDataContentDisposition, - inputStream: InputStream, storeType: StoreTypeEnum, storeCode: String, - version: String + version: String, + storePkgEnvInfos: List? ) { - handlePkgFile( - disposition = disposition, - inputStream = inputStream, - storeType = storeType, - storeCode = storeCode, - version = version - ) + val storeArchivePath = buildStoreArchivePath(storeType, storeCode, version) + storePkgEnvInfos?.forEach { storePkgEnvInfo -> + val pkgLocalPath = storePkgEnvInfo.pkgLocalPath + if (pkgLocalPath.isNullOrBlank()) { + return@forEach + } + val file = File(storeArchivePath, pkgLocalPath) + if (!file.exists()) { + logger.warn("uploadLocalFile file[$pkgLocalPath] not exist!!") + return@forEach + } + val pkgRepoPath = generatePkgRepoPath( + storeCode = storeCode, + version = version, + pkgFileName = file.name, + osName = storePkgEnvInfo.osName, + osArch = storePkgEnvInfo.osArch + ) + file.renameTo(File(storeArchivePath, pkgRepoPath)) + } } override fun getStoreFileContent(filePath: String, storeType: StoreTypeEnum, repoName: String?): String { diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceMonitorSpaceResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceMonitorSpaceResource.kt index 24996c508da5..e8efb798c060 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceMonitorSpaceResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceMonitorSpaceResource.kt @@ -58,6 +58,14 @@ interface ServiceMonitorSpaceResource { projectCode: String ): Result + @Operation(summary = "获取监控空间业务id") + @POST + @Path("/listMonitorSpaceBizIds") + fun listMonitorSpaceBizIds( + @Parameter(description = "项目ID列表", required = false) + projectCodes: List + ): Result> + @POST @Path("/migrateMonitorResource") @Operation(summary = "迁移监控空间权限资源") diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt index 31fc7fdb84c6..57b9c63191dc 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/service/ServiceProjectAuthResource.kt @@ -27,8 +27,10 @@ package com.tencent.devops.auth.api.service +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ProjectPermissionInfoVO import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_BK_TOKEN +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_GIT_TYPE import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Result @@ -115,7 +117,7 @@ interface ServiceProjectAuthResource { @GET @Path("/{projectCode}/users/{userId}/isProjectUsers") - @Operation(summary = "判断是否某个项目中某个组角色的成员") + @Operation(summary = "校验用户是否有访问项目权限") fun isProjectUser( @HeaderParam(AUTH_HEADER_DEVOPS_BK_TOKEN) @Parameter(description = "认证token", required = true) @@ -134,6 +136,18 @@ interface ServiceProjectAuthResource { group: BkAuthGroup? = null ): Result + @GET + @Path("/{projectCode}/users/{userId}/isProjectMember") + @Operation(summary = "校验用户是否是项目成员") + fun isProjectMember( + @PathParam("userId") + @Parameter(description = "用户Id", required = true) + userId: String, + @PathParam("projectCode") + @Parameter(description = "项目Code", required = true) + projectCode: String + ): Result + @GET @Path("/{projectCode}/users/{userId}/checkUserInProjectLevelGroup") @Operation(summary = "是否该用户在项目级别的组中") @@ -247,4 +261,13 @@ interface ServiceProjectAuthResource { @Parameter(description = "项目Code", required = true) projectCode: String ): Result + + @GET + @Path("/listUserProjectsWithAuthorization") + @Operation(summary = "获取用户授权相关的项目") + fun listUserProjectsWithAuthorization( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @Parameter(description = "用户ID", required = true) + userId: String + ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt index 0f672983e720..1a57ff934aaa 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthAuthorizationResource.kt @@ -28,7 +28,10 @@ package com.tencent.devops.auth.api.user +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE import com.tencent.devops.common.api.model.SQLPage @@ -68,6 +71,9 @@ interface UserAuthAuthorizationResource { @Parameter(description = "项目ID", required = true) @PathParam("projectId") projectId: String, + @Parameter(description = "操作渠道", required = true) + @QueryParam("operateChannel") + operateChannel: OperateChannel?, @Parameter(description = "查询条件", required = true) condition: ResourceAuthorizationConditionRequest ): Result> @@ -138,4 +144,13 @@ interface UserAuthAuthorizationResource { @Parameter(description = "资源授权交接条件实体", required = true) condition: ResetAllResourceAuthorizationReq ): Result> + + @GET + @Path("/listUserProjectsWithAuthorization") + @Operation(summary = "获取用户授权相关的项目") + fun listUserProjectsWithAuthorization( + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + @Parameter(description = "用户ID", required = true) + userId: String + ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt new file mode 100644 index 000000000000..cb24bde8d7c6 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthHandoverResource.kt @@ -0,0 +1,112 @@ +package com.tencent.devops.auth.api.user + +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewBatchUpdateReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType + +@Tag(name = "USER_RESOURCE_AUTHORIZATION", description = "用户-权限-交接相关") +@Path("/user/auth/handover/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface UserAuthHandoverResource { + @POST + @Path("/{projectId}/handoverAuthorizationsApplication") + @Operation(summary = "交接授权申请") + fun handoverAuthorizationsApplication( + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "资源授权交接条件实体", required = true) + condition: ResourceAuthorizationHandoverConditionRequest + ): Result + + @POST + @Path("/listHandoverOverviews") + @Operation(summary = "权限交接总览列表") + fun listHandoverOverviews( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接总览查询", required = true) + queryRequest: HandoverOverviewQueryReq + ): Result> + + @POST + @Path("/getResourceType2CountOfHandover") + @Operation(summary = "获取资源授权管理数量") + fun getResourceType2CountOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "查询请求体", required = true) + queryReq: ResourceType2CountOfHandoverQuery + ): Result> + + @POST + @Path("/listAuthorizationsOfHandover") + @Operation(summary = "获取交接单中授权相关") + fun listAuthorizationsOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接详细查询请求体", required = true) + queryReq: HandoverDetailsQueryReq + ): Result> + + @POST + @Path("/listGroupsOfHandover") + @Operation(summary = "获取交接单中用户组相关") + fun listGroupsOfHandover( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "权限交接详细查询请求体", required = true) + queryReq: HandoverDetailsQueryReq + ): Result> + + @POST + @Path("/handleHanoverApplication") + @Operation(summary = "处理交接审批单") + fun handleHanoverApplication( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "更新权限交接总览请求体", required = true) + request: HandoverOverviewUpdateReq + ): Result + + @POST + @Path("/batchHandleHanoverApplications") + @Operation(summary = "批量处理交接审批单") + fun batchHandleHanoverApplications( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "批量更新权限交接总览请求体", required = true) + request: HandoverOverviewBatchUpdateReq + ): Result +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt index 5dc4c0342674..be36c7f632ce 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.api.user import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupPoliciesVo import com.tencent.devops.common.api.annotation.BkInterfaceI18n @@ -110,6 +111,9 @@ interface UserAuthResourceGroupResource { @QueryParam("action") @Parameter(description = "操作") action: String?, + @QueryParam("operateChannel") + @Parameter(description = "操作渠道") + operateChannel: OperateChannel?, @Parameter(description = "起始位置,从0开始") @QueryParam("start") start: Int, @@ -118,6 +122,27 @@ interface UserAuthResourceGroupResource { limit: Int ): Result> + @GET + @Path("{groupId}/getMemberGroupDetails/") + @Operation(summary = "获取用户加入单个组的详情") + fun getMemberGroupDetails( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "资源类型", required = true) + @PathParam("resourceType") + resourceType: String, + @Parameter(description = "用户组Id") + @PathParam("groupId") + groupId: Int, + @QueryParam("memberId") + @Parameter(description = "组织ID/成员ID") + memberId: String + ): Result + @PUT @Path("{groupId}/member/renewal") @Operation(summary = "用户续期") diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt index 26a74ba5b87c..1fc810115126 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt @@ -2,15 +2,17 @@ package com.tencent.devops.auth.api.user import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result @@ -96,8 +98,8 @@ interface UserAuthResourceMemberResource { @PUT @Path("/batch/renewal") - @Operation(summary = "批量续期组成员权限--无需进行审批") - fun batchRenewalGroupMembers( + @Operation(summary = "批量续期组成员权限--管理员视角") + fun batchRenewalGroupMembersFromManager( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -110,8 +112,8 @@ interface UserAuthResourceMemberResource { @DELETE @Path("/batch/remove") - @Operation(summary = "批量移除用户组成员") - fun batchRemoveGroupMembers( + @Operation(summary = "批量移除用户组成员--管理员视角") + fun batchRemoveGroupMembersFromManager( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -119,13 +121,47 @@ interface UserAuthResourceMemberResource { @PathParam("projectId") projectId: String, @Parameter(description = "批量移除成员请求实体") - removeMemberDTO: GroupMemberCommonConditionReq + removeMemberDTO: GroupMemberRemoveConditionReq + ): Result + + @DELETE + @Path("/batch/personal/remove") + @Operation(summary = "批量退出用户组成员--个人视角") + fun batchRemoveGroupMembersFromPersonal( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "批量移除成员请求实体") + removeMemberDTO: GroupMemberRemoveConditionReq + ): Result + + @DELETE + @Path("/single/{groupId}/{operateChannel}/remove") + @Operation(summary = "退出单个组") + fun deleteResourceGroupMembers( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "组ID", required = true) + @PathParam("groupId") + groupId: Int, + @Parameter(description = "操作渠道", required = true) + @PathParam("operateChannel") + operateChannel: OperateChannel, + @Parameter(description = "操作对象", required = true) + targetMember: ResourceMemberInfo ): Result @PUT @Path("/batch/handover") - @Operation(summary = "批量交接用户组成员") - fun batchHandoverGroupMembers( + @Operation(summary = "批量交接用户组成员--管理员视角") + fun batchHandoverGroupMembersFromManager( @Parameter(description = "用户名", required = true) @HeaderParam(AUTH_HEADER_USER_ID) userId: String, @@ -136,6 +172,20 @@ interface UserAuthResourceMemberResource { handoverMemberDTO: GroupMemberHandoverConditionReq ): Result + @PUT + @Path("/batch/personal/handover") + @Operation(summary = "批量交接用户组成员--个人视角") + fun batchHandoverApplicationFromPersonal( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "批量交接成员请求实体") + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Result + @POST @Path("/batch/{batchOperateType}/check/") @Operation(summary = "批量操作用户组检查") @@ -211,6 +261,9 @@ interface UserAuthResourceMemberResource { relatedResourceCode: String?, @QueryParam("action") @Parameter(description = "操作") - action: String? - ): Result> + action: String?, + @QueryParam("operateChannel") + @Parameter(description = "操作渠道") + operateChannel: OperateChannel? + ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceResource.kt index aa0f259134df..0f648870d3d2 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceResource.kt @@ -34,9 +34,9 @@ import com.tencent.devops.auth.pojo.vo.IamGroupMemberInfoVo import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Pagination import com.tencent.devops.common.api.pojo.Result -import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag import javax.ws.rs.Consumes import javax.ws.rs.GET import javax.ws.rs.HeaderParam diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt index 4be2b229dfc4..a6ecd7673092 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthI18nConstants.kt @@ -47,4 +47,9 @@ object AuthI18nConstants { const val BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED = "bkMemberExpiredAtDisplayExpired" // 有效期: 已过期 const val BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL = "bkMemberExpiredAtDisplayNormal" // 有效期: {0}天 const val BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT = "bkMemberExpiredAtDisplayPermanent" // 有效期: 永久 + + const val BK_APPLY_TO_HANDOVER = "bkApplyToHandover" // 申请移交 + const val BK_HANDOVER_GROUPS = "bkHandoverGroups" // 个权限用户组 + const val BK_HANDOVER_AUTHORIZATIONS = "bkHandoverAuthorizations" // 个授权 + const val BK_PROJECT = "bk_project" // 蓝盾项目 } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt index 4b66ce68fbbb..fcc1f10c5761 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/constant/AuthMessageCode.kt @@ -142,4 +142,12 @@ object AuthMessageCode { const val INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER = "2121089" // 已过期的权限不允许交接 const val ERROR_USER_INFORMATION_NOT_SYNCED = "2121090" // 请等待第二天用户信息同步后再尝试操作,因为新入职用户的信息尚未同步完成。 + + const val ERROR_HANDOVER_OVERVIEW_NOT_EXIST = "2121091" // 权限交接记录不存在 + const val ERROR_HANDOVER_FINISH = "2121092" // 该交接申请单已被处理,不允许重复操作 + const val ERROR_HANDOVER_REVOKE = "2121093" // 由于您不是该交接申请单的发起人,无法进行撤销操作 + const val ERROR_HANDOVER_APPROVAL = "2121094" // 由于您不是该交接申请单的审批人,无法进行任何操作 + const val ERROR_HANDOVER_HANDLE = "2121095" // 该交接申请单正在被处理中,请耐心等待 + const val ERROR_REPERTORY_HANDOVER_AUTHORIZATION = "2121096" // 交接操作不合法,用户没有对应代码库授权的权限 + const val ERROR_SINGLE_GROUP_REMOVE = "2121098" // 由于直接退出用户组,会导致授权失效,必须进行用户组移交 } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt new file mode 100644 index 000000000000..a5fec007c00d --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverDetailDTO.kt @@ -0,0 +1,20 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.enum.HandoverType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接详细表") +data class HandoverDetailDTO( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "流程单号") + var flowNo: String? = null, + @get:Schema(title = "授权/组ID") + val itemId: String, + @get:Schema(title = "组/授权资源关联的资源类型") + val resourceType: String, + @get:Schema(title = "交接类型") + val handoverType: HandoverType, + @get:Schema(title = "审批人") + var approver: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt new file mode 100644 index 000000000000..18681ce365a6 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/HandoverOverviewCreateDTO.kt @@ -0,0 +1,28 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "创建权限交接总览DTO") +data class HandoverOverviewCreateDTO( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "项目名称") + val projectName: String, + @get:Schema(title = "项目ID") + var title: String? = null, + @get:Schema(title = "流程单号") + var flowNo: String? = null, + @get:Schema(title = "申请人") + val applicant: String, + @get:Schema(title = "审批人") + val approver: String, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus, + @get:Schema(title = "用户组个数") + val groupCount: Int, + @get:Schema(title = "授权个数") + val authorizationCount: Int, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt new file mode 100644 index 000000000000..7fe5aced9e3d --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/InvalidAuthorizationsDTO.kt @@ -0,0 +1,19 @@ +package com.tencent.devops.auth.pojo.dto + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "移除/移交用户组成员导致的无效授权") +data class InvalidAuthorizationsDTO( + @get:Schema(title = "引起代持人权限失效的用户组") + val invalidGroupIds: List, + @get:Schema(title = "引起代持人权限失效的流水线") + val invalidPipelineIds: List, + @get:Schema(title = "引起oauth失效的代码库") + val invalidRepertoryIds: List = emptyList(), + @get:Schema(title = "失效的CMDB环境节点") + val invalidEnvNodeIds: List = emptyList() +) { + fun isHasInvalidAuthorizations(): Boolean { + return invalidRepertoryIds.isNotEmpty() || invalidPipelineIds.isNotEmpty() || invalidEnvNodeIds.isNotEmpty() + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/MemberGroupJoinedDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/MemberGroupJoinedDTO.kt new file mode 100644 index 000000000000..0d25b99c8495 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/MemberGroupJoinedDTO.kt @@ -0,0 +1,11 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.enum.MemberType +import io.swagger.v3.oas.annotations.media.Schema + +data class MemberGroupJoinedDTO( + @get:Schema(title = "组id") + val id: Int, + @get:Schema(title = "组成员类型") + val memberType: MemberType +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/CollationType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/CollationType.kt new file mode 100644 index 000000000000..bd3ff41bc50a --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/CollationType.kt @@ -0,0 +1,7 @@ +package com.tencent.devops.auth.pojo.enum + +enum class CollationType { + ASC, + + DESC; +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt new file mode 100644 index 000000000000..d4535c868c6a --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverAction.kt @@ -0,0 +1,41 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverAction( + val value: Int, + val alias: String, + val emailContent: String, + val weworkContent: String +) { + // 审批成功 + AGREE( + 1, + "已通过", + "你提交的权限交接单 %s 已被 %s 通过。恭喜你完成交接。", + "你提交的权限交接单 %s 已被 %s 通过。恭喜你完成交接。", + ), + + // 审批驳回 + REJECT( + 2, + "已拒绝", + "你提交的权限交接单 %s 已被 %s 拒绝。请重新交接。", + "你提交的权限交接单 %s 已被 %s 拒绝。请重新交接。" + ), + + // 撤销 + REVOKE( + 3, + "撤销", + "已撤销", + "已撤销" + ); + + companion object { + fun get(value: Int): HandoverAction { + HandoverAction.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt new file mode 100644 index 000000000000..352a6c6bbe9c --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverQueryChannel.kt @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.auth.pojo.enum + +enum class HandoverQueryChannel { + PREVIEW, + + HANDOVER_APPLICATION +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt new file mode 100644 index 000000000000..aaf82e6e9acb --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverStatus.kt @@ -0,0 +1,24 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverStatus(val value: Int) { + // 审批中 + PENDING(0), + + // 审批成功 + SUCCEED(1), + + // 审批驳回 + REJECT(2), + + // 撤销 + REVOKE(3); + + companion object { + fun get(value: Int): HandoverStatus { + HandoverStatus.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt new file mode 100644 index 000000000000..5d3dce177853 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/HandoverType.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.enum + +enum class HandoverType(val value: String, val alias: String) { + // 用户组 + GROUP("group", "用户组"), + + // 授权 + AUTHORIZATION("authorization", "授权管理"); + + companion object { + fun get(value: String): HandoverType { + HandoverType.values().forEach { + if (value == it.value) return it + } + throw IllegalArgumentException("No enum for constant $value") + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt index 06b700c88bdb..8bff11d48701 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/JoinedType.kt @@ -32,5 +32,8 @@ enum class JoinedType { DIRECT, // 通过模板加入 - TEMPLATE + TEMPLATE, + + // 通过组织加入 + DEPARTMENT } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt new file mode 100644 index 000000000000..5c08753de64d --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/OperateChannel.kt @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + * + */ + +package com.tencent.devops.auth.pojo.enum + +enum class OperateChannel(val value: String) { + // 个人视角 + PERSONAL("personal"), + + // 管理员视角 + MANAGER("manager"); +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt index fc06012edd14..55064743eb0f 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/RemoveMemberButtonControl.kt @@ -37,6 +37,9 @@ enum class RemoveMemberButtonControl { // 通过模板加入,不允许移出组 TEMPLATE, + // 用户通过组织 间接加入,不允许移出组 + DEPARTMENT, + // 其他,允许移出组 OTHER } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/SortType.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/SortType.kt new file mode 100644 index 000000000000..1ae8dc933fe1 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/enum/SortType.kt @@ -0,0 +1,7 @@ +package com.tencent.devops.auth.pojo.enum + +enum class SortType { + FLOW_NO, + + CREATE_TIME; +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt index db943b94c74d..651969f3bd00 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberCommonConditionReq.kt @@ -1,24 +1,25 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户组成员处理公共请求体") open class GroupMemberCommonConditionReq( @get:Schema(title = "组IDs") - open val groupIds: List = emptyList(), + open val groupIds: List = emptyList(), @get:Schema(title = "全选的资源类型") open val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") open val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - open var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") - open val targetMember: ResourceMemberInfo + open val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + open val operateChannel: OperateChannel = OperateChannel.MANAGER ) { override fun toString(): String { return "GroupMemberCommonConditionReq(groupIds=$groupIds,resourceTypes=$resourceTypes," + - "allSelection=$allSelection,excludedUniqueManagerGroup=$excludedUniqueManagerGroup," + - "targetMember=$targetMember)" + "allSelection=$allSelection,targetMember=$targetMember,operateChannel=$operateChannel)" } } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt index dc0f85b0daa7..bd53c502ae31 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberHandoverConditionReq.kt @@ -29,28 +29,30 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.constant.AuthMessageCode.INVALID_HANDOVER_TO import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.common.api.exception.ErrorCodeException import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户组成员交接条件请求体") data class GroupMemberHandoverConditionReq( @get:Schema(title = "组IDs") - override val groupIds: List = emptyList(), + override val groupIds: List = emptyList(), @get:Schema(title = "全选的资源类型") override val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") override val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - override var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, @get:Schema(title = "授予人") val handoverTo: ResourceMemberInfo ) : GroupMemberCommonConditionReq( groupIds = groupIds, resourceTypes = resourceTypes, allSelection = allSelection, - excludedUniqueManagerGroup = excludedUniqueManagerGroup, + operateChannel = operateChannel, targetMember = targetMember ) { fun checkHandoverTo() { diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt new file mode 100644 index 000000000000..c801a2941342 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRemoveConditionReq.kt @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.constant.AuthMessageCode.INVALID_HANDOVER_TO +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.common.api.exception.ErrorCodeException +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户组成员移除条件请求体") +data class GroupMemberRemoveConditionReq( + @get:Schema(title = "组IDs") + override val groupIds: List = emptyList(), + @get:Schema(title = "全选的资源类型") + override val resourceTypes: List = emptyList(), + @get:Schema(title = "全量选择") + override val allSelection: Boolean = false, + @get:Schema(title = "目标对象") + override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, + @get:Schema(title = "授予人") + val handoverTo: ResourceMemberInfo? = null +) : GroupMemberCommonConditionReq( + groupIds = groupIds, + resourceTypes = resourceTypes, + allSelection = allSelection, + operateChannel = operateChannel, + targetMember = targetMember +) { + fun checkHandoverTo() { + if (handoverTo == null || handoverTo.id == targetMember.id) { + throw ErrorCodeException( + errorCode = INVALID_HANDOVER_TO + ) + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt index b8b6ce8598c9..37c085a607c2 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/GroupMemberRenewalConditionReq.kt @@ -28,26 +28,28 @@ package com.tencent.devops.auth.pojo.request import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户组成员续期") data class GroupMemberRenewalConditionReq( @get:Schema(title = "组IDs") - override val groupIds: List, + override val groupIds: List, @get:Schema(title = "全选某种资源类型下的用户组") override val resourceTypes: List = emptyList(), @get:Schema(title = "全量选择") override val allSelection: Boolean = false, - @get:Schema(title = "是否排除唯一管理员组") - override var excludedUniqueManagerGroup: Boolean = false, @get:Schema(title = "目标对象") override val targetMember: ResourceMemberInfo, + @get:Schema(title = "操作渠道") + override val operateChannel: OperateChannel = OperateChannel.MANAGER, @get:Schema(title = "续期时长(天)") val renewalDuration: Int ) : GroupMemberCommonConditionReq( groupIds = groupIds, resourceTypes = resourceTypes, allSelection = allSelection, - excludedUniqueManagerGroup = excludedUniqueManagerGroup, + operateChannel = operateChannel, targetMember = targetMember ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt new file mode 100644 index 000000000000..81065ed94d44 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverDetailsQueryReq.kt @@ -0,0 +1,38 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接详细查询请求体") +data class HandoverDetailsQueryReq( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "组/授权资源关联的资源类型") + val resourceType: String, + @get:Schema(title = "流程单号") + val flowNo: String?, + @get:Schema(title = "交接预览请求条件") + val previewConditionReq: GroupMemberCommonConditionReq?, + @get:Schema(title = "渠道") + val queryChannel: HandoverQueryChannel, + @get:Schema(title = "第几页") + val page: Int, + @get:Schema(title = "每页大小") + val pageSize: Int +) { + fun check() { + when (queryChannel) { + HandoverQueryChannel.HANDOVER_APPLICATION -> { + if (flowNo == null) { + throw IllegalArgumentException("flowNo cannot be null!") + } + } + + else -> { + if (previewConditionReq == null) { + throw IllegalArgumentException("previewConditionReq can not be null!") + } + } + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewBatchUpdateReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewBatchUpdateReq.kt new file mode 100644 index 000000000000..bc15c9e8e2c7 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewBatchUpdateReq.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverAction +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "批量更新权限交接请求体") +data class HandoverOverviewBatchUpdateReq( + @get:Schema(title = "流程单号") + val flowNos: List = emptyList(), + @get:Schema(title = "是否全选") + val allSelection: Boolean = false, + @get:Schema(title = "操作人") + val operator: String, + @get:Schema(title = "审批操作") + val handoverAction: HandoverAction, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt new file mode 100644 index 000000000000..0b5bbcdf76c1 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewQueryReq.kt @@ -0,0 +1,40 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.CollationType +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.SortType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "权限交接总览查询") +data class HandoverOverviewQueryReq( + @get:Schema(title = "成员ID") + val memberId: String, + @get:Schema(title = "项目ID") + val projectCode: String? = null, + @get:Schema(title = "项目名称") + val projectName: String? = null, + @get:Schema(title = "项目ID") + val title: String? = null, + @get:Schema(title = "流程单号") + val flowNo: String? = null, + @get:Schema(title = "流程单号列表") + val flowNos: List? = null, + @get:Schema(title = "申请人") + val applicant: String? = null, + @get:Schema(title = "审批人") + val approver: String? = null, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus? = null, + @get:Schema(title = "最小提单时间") + val minCreatedTime: Long? = null, + @get:Schema(title = "最打提单时间") + val maxCreatedTime: Long? = null, + @get:Schema(title = "排序类型") + val sortType: SortType? = null, + @get:Schema(title = "排序类型") + val collationType: CollationType? = null, + @get:Schema(title = "页数") + val page: Int? = null, + @get:Schema(title = "页大小") + val pageSize: Int? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt new file mode 100644 index 000000000000..baefcac6c0e4 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/HandoverOverviewUpdateReq.kt @@ -0,0 +1,18 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.HandoverAction +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "更新权限交接总览请求体") +data class HandoverOverviewUpdateReq( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "流程单号") + val flowNo: String, + @get:Schema(title = "操作人") + val operator: String, + @get:Schema(title = "审批操作") + val handoverAction: HandoverAction, + @get:Schema(title = "备注") + val remark: String? = "" +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt new file mode 100644 index 000000000000..a3c0cd1c298a --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ResourceType2CountOfHandoverQuery.kt @@ -0,0 +1,34 @@ +package com.tencent.devops.auth.pojo.request + +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import io.swagger.v3.oas.annotations.media.Schema + +data class ResourceType2CountOfHandoverQuery( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "渠道") + val queryChannel: HandoverQueryChannel, + @get:Schema(title = "流程单号") + val flowNo: String?, + @get:Schema(title = "交接预览请求条件") + val previewConditionReq: GroupMemberCommonConditionReq?, + @get:Schema(title = "批量操作动作") + val batchOperateType: BatchOperateType? +) { + fun check() { + when (queryChannel) { + HandoverQueryChannel.HANDOVER_APPLICATION -> { + if (flowNo == null) { + throw IllegalArgumentException("flowNo cannot be null!") + } + } + + else -> { + if (previewConditionReq == null || batchOperateType == null) { + throw IllegalArgumentException("previewConditionReq or batchOperateType can not be null!") + } + } + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/AuthProjectVO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/AuthProjectVO.kt new file mode 100644 index 000000000000..7aa6eac5485e --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/AuthProjectVO.kt @@ -0,0 +1,11 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "项目返回体") +data class AuthProjectVO( + @get:Schema(title = "数量") + val projectCode: String, + @get:Schema(title = "项目") + val projectName: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt index 501eb12299c5..7e862bd4ec11 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/BatchOperateGroupMemberCheckVo.kt @@ -6,6 +6,22 @@ import io.swagger.v3.oas.annotations.media.Schema data class BatchOperateGroupMemberCheckVo( @get:Schema(title = "总数") val totalCount: Int, + @get:Schema(title = "可操作的数量") + val operableCount: Int? = 0, @get:Schema(title = "无法操作的数量") - val inoperableCount: Int? = null + val inoperableCount: Int? = 0, + @get:Schema(title = "唯一管理员组的数量") + val uniqueManagerCount: Int? = 0, + @get:Schema(title = "导致代持人失效的用户组") + val invalidGroupCount: Int? = 0, + @get:Schema(title = "无效的流水线授权数量") + val invalidPipelineAuthorizationCount: Int? = 0, + @get:Schema(title = "无效的代码库授权数量") + val invalidRepositoryAuthorizationCount: Int? = 0, + @get:Schema(title = "无效的环境节点授权数量") + val invalidEnvNodeAuthorizationCount: Int? = 0, + @get:Schema(title = "可交接的组数量") + val canHandoverCount: Int? = 0, + @get:Schema(title = "是否需要交接") + val needToHandover: Boolean? = null ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt index 6193dda5c6ef..846dd81c6817 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/GroupDetailsInfoVo.kt @@ -1,6 +1,7 @@ package com.tencent.devops.auth.pojo.vo import com.tencent.devops.auth.pojo.enum.JoinedType +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl import io.swagger.v3.oas.annotations.media.Schema @@ -29,5 +30,11 @@ data class GroupDetailsInfoVo( @get:Schema(title = "加入方式") val joinedType: JoinedType, @get:Schema(title = "操作人") - val operator: String + val operator: String, + @get:Schema(title = "是否正在交接") + val beingHandedOver: Boolean? = null, + @get:Schema(title = "交接单号") + val flowNo: String? = null, + @get:Schema(title = "组成员类型") + val memberType: MemberType? = null ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt new file mode 100644 index 000000000000..0828981a8d45 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverAuthorizationDetailVo.kt @@ -0,0 +1,16 @@ +package com.tencent.devops.auth.pojo.vo + +import com.tencent.devops.auth.pojo.enum.HandoverType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "授权交接详细表") +data class HandoverAuthorizationDetailVo( + @get:Schema(title = "授权资源ID") + val resourceCode: String, + @get:Schema(title = "授权资源名称") + val resourceName: String, + @get:Schema(title = "交接类型") + val handoverType: HandoverType, + @get:Schema(title = "授权人") + val handoverFrom: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt new file mode 100644 index 000000000000..6a6e3e40e82b --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverGroupDetailVo.kt @@ -0,0 +1,19 @@ +package com.tencent.devops.auth.pojo.vo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户组交接详细返回体") +data class HandoverGroupDetailVo( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "组ID") + val iamGroupId: Int, + @get:Schema(title = "组名称") + val groupName: String, + @get:Schema(title = "组描述") + val groupDesc: String? = null, + @get:Schema(title = "关联的资源ID") + val resourceCode: String, + @get:Schema(title = "关联的资源名称") + val resourceName: String +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt new file mode 100644 index 000000000000..7069614b4a67 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/HandoverOverviewVo.kt @@ -0,0 +1,39 @@ +package com.tencent.devops.auth.pojo.vo + +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDateTime + +@Schema(title = "权限交接总览返回体") +data class HandoverOverviewVo( + @get:Schema(title = "ID") + val id: Long, + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "项目名称") + val projectName: String, + @get:Schema(title = "标题") + val title: String, + @get:Schema(title = "流程单号") + val flowNo: String, + @get:Schema(title = "申请人") + val applicant: String, + @get:Schema(title = "审批人") + val approver: String, + @get:Schema(title = "审批结果") + val handoverStatus: HandoverStatus, + @get:Schema(title = "用户组个数") + val groupCount: Int, + @get:Schema(title = "授权个数") + val authorizationCount: Int, + @get:Schema(title = "创建时间") + val createTime: LocalDateTime, + @get:Schema(title = "最后修改人") + val lastOperator: String? = null, + @get:Schema(title = "是否可以撤销,提单为当前用户并且单据处于审批中") + val canRevoke: Boolean? = null, + @get:Schema(title = "是否可以审批,审批人为当前用户并且单据处于审批中") + val canApproval: Boolean? = null, + @get:Schema(title = "备注") + val remark: String? = null +) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt similarity index 63% rename from src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt rename to src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt index 293d3646046c..49d6a24ff37e 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/MemberGroupCountWithPermissionsVo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/vo/ResourceType2CountVo.kt @@ -1,13 +1,16 @@ package com.tencent.devops.auth.pojo.vo +import com.tencent.devops.auth.pojo.enum.HandoverType import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "用户有权限的用户组数量") -data class MemberGroupCountWithPermissionsVo( +data class ResourceType2CountVo( @get:Schema(title = "资源类型") val resourceType: String, @get:Schema(title = "资源类型名") val resourceTypeName: String, @get:Schema(title = "数量") - val count: Long + val count: Long, + @get:Schema(title = "类型") + val type: HandoverType = HandoverType.GROUP ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt index 128879990fb7..a1440f8369f7 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt @@ -71,9 +71,9 @@ class AuthCronSyncGroupAndMember( } /** - * 1小时同步一次用户过期时间 + * 10分钟同步一次用户过期时间 * */ - @Scheduled(initialDelay = 1000, fixedRate = 3600000) + @Scheduled(initialDelay = 1000, fixedRate = 600000) fun syncGroupMemberExpiredTimeRegularly() { if (!enable) { return diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt index dca6f34e966d..4b30e0176127 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt @@ -73,7 +73,6 @@ class AuthAuthorizationDao { .set(HANDOVER_FROM_CN_NAME, resourceAuthorizationDto.handoverFromCnName) .set(RESOURCE_NAME, resourceAuthorizationDto.resourceName) .set(HANDOVER_TIME, handoverDateTime) - .where(CREATE_TIME.eq(UPDATE_TIME)) .execute() } } @@ -158,6 +157,7 @@ class AuthAuthorizationDao { return with(TAuthResourceAuthorization.T_AUTH_RESOURCE_AUTHORIZATION) { dslContext.selectFrom(this) .where(buildQueryCondition(condition)) + .orderBy(HANDOVER_TIME.desc()) .let { if (condition.page != null && condition.pageSize != null) { it.limit((condition.page!! - 1) * condition.pageSize!!, condition.pageSize) @@ -191,6 +191,12 @@ class AuthAuthorizationDao { if (resourceName != null) { conditions.add(RESOURCE_NAME.like("%$resourceName%")) } + if (!filterResourceCodes.isNullOrEmpty()) { + conditions.add(RESOURCE_CODE.`in`(filterResourceCodes)) + } + if (!excludeResourceCodes.isNullOrEmpty()) { + conditions.add(RESOURCE_CODE.notIn(excludeResourceCodes)) + } if (handoverFrom != null) { conditions.add(HANDOVER_FROM.eq(handoverFrom)) } @@ -202,8 +208,22 @@ class AuthAuthorizationDao { return conditions } + fun listUserProjects( + dslContext: DSLContext, + userId: String + ): List { + return with(TAuthResourceAuthorization.T_AUTH_RESOURCE_AUTHORIZATION) { + dslContext.select(PROJECT_CODE) + .from(this) + .where(HANDOVER_FROM.eq(userId)) + .groupBy(PROJECT_CODE) + .fetch().map { it.value1() } + } + } + fun TAuthResourceAuthorizationRecord.convert(): ResourceAuthorizationResponse { return ResourceAuthorizationResponse( + id = id, projectCode = projectCode, resourceType = resourceType, resourceName = resourceName, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt new file mode 100644 index 000000000000..73a5ac000dc9 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverDetailDao.kt @@ -0,0 +1,128 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.model.auth.tables.TAuthHandoverDetail +import com.tencent.devops.model.auth.tables.records.TAuthHandoverDetailRecord +import org.jooq.Condition +import org.jooq.DSLContext +import org.jooq.impl.DSL.count +import org.springframework.stereotype.Repository + +@Repository +class AuthHandoverDetailDao { + fun batchCreate( + dslContext: DSLContext, + handoverDetailDTOs: List + ) { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + handoverDetailDTOs.forEach { + dslContext.insertInto( + this, + PROJECT_CODE, + FLOW_NO, + ITEM_ID, + RESOURCE_TYPE, + HANDOVER_TYPE + ).values( + it.projectCode, + it.flowNo, + it.itemId, + it.resourceType, + it.handoverType.value + ).execute() + } + } + } + + fun list( + dslContext: DSLContext, + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): List { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.selectFrom(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ) + ).fetch().map { it.convert() } + } + } + + fun count( + dslContext: DSLContext, + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): Long { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.selectCount().from(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ) + ).fetchOne(0, Long::class.java)!! + } + } + + fun countWithResourceType( + dslContext: DSLContext, + projectCode: String, + flowNo: String, + handoverType: HandoverType? + ): Map { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + return dslContext.select(RESOURCE_TYPE, count()) + .from(this) + .where( + buildQueryConditions( + projectCode = projectCode, + flowNos = listOf(flowNo), + resourceType = null, + handoverType = handoverType + ) + ).groupBy(RESOURCE_TYPE) + .fetch().map { Pair(it.value1(), it.value2().toLong()) }.toMap() + } + } + + private fun buildQueryConditions( + projectCode: String, + flowNos: List, + resourceType: String?, + handoverType: HandoverType? + ): List { + with(TAuthHandoverDetail.T_AUTH_HANDOVER_DETAIL) { + val conditions = mutableListOf() + conditions.add(PROJECT_CODE.eq(projectCode)) + conditions.add(FLOW_NO.`in`(flowNos)) + resourceType?.let { + conditions.add(RESOURCE_TYPE.eq(resourceType)) + } + handoverType?.let { + conditions.add(HANDOVER_TYPE.eq(handoverType.value)) + } + return conditions + } + } + + private fun TAuthHandoverDetailRecord.convert(): HandoverDetailDTO { + return HandoverDetailDTO( + projectCode = projectCode, + flowNo = flowNo, + itemId = itemId, + resourceType = resourceType, + handoverType = HandoverType.get(handoverType) + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt new file mode 100644 index 000000000000..0a33192d780d --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthHandoverOverviewDao.kt @@ -0,0 +1,191 @@ +package com.tencent.devops.auth.dao + +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.CollationType +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.SortType +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.db.utils.skipCheck +import com.tencent.devops.model.auth.tables.TAuthHandoverOverview +import com.tencent.devops.model.auth.tables.records.TAuthHandoverOverviewRecord +import org.jooq.Condition +import org.jooq.DSLContext +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class AuthHandoverOverviewDao { + fun create( + dslContext: DSLContext, + overviewDTO: HandoverOverviewCreateDTO + ) { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.insertInto( + this, + PROJECT_CODE, + PROJECT_NAME, + TITLE, + FLOW_NO, + APPLICANT, + APPROVER, + STATUS, + GROUP_COUNT, + AUTHORIZATION_COUNT, + REMARK + ).values( + overviewDTO.projectCode, + overviewDTO.projectName, + overviewDTO.title, + overviewDTO.flowNo, + overviewDTO.applicant, + overviewDTO.approver, + overviewDTO.handoverStatus.value, + overviewDTO.groupCount, + overviewDTO.authorizationCount, + overviewDTO.remark + ).execute() + } + } + + fun update( + dslContext: DSLContext, + overviewDTO: HandoverOverviewUpdateReq + ) { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.update(this) + .set(STATUS, overviewDTO.handoverAction.value) + .let { if (overviewDTO.remark != null) it.set(REMARK, overviewDTO.remark) else it } + .set(UPDATE_TIME, LocalDateTime.now()) + .set(LAST_OPERATOR, overviewDTO.operator) + .where(FLOW_NO.eq(overviewDTO.flowNo)) + .and(PROJECT_CODE.eq(overviewDTO.projectCode)) + .execute() + } + } + + fun get( + dslContext: DSLContext, + flowNo: String + ): HandoverOverviewVo? { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectFrom(this) + .where(FLOW_NO.eq(flowNo)) + .fetchAny()?.convert() + } + } + + fun list( + dslContext: DSLContext, + queryRequest: HandoverOverviewQueryReq + ): List { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectFrom(this) + .where(buildQueryConditions(queryRequest)) + .let { + when { + queryRequest.sortType == SortType.FLOW_NO && + queryRequest.collationType == CollationType.ASC -> { + it.orderBy(FLOW_NO.asc()) + } + + queryRequest.sortType == SortType.FLOW_NO && + queryRequest.collationType == CollationType.DESC -> { + it.orderBy(FLOW_NO.desc()) + } + + queryRequest.sortType == SortType.CREATE_TIME && + queryRequest.collationType == CollationType.ASC -> { + it.orderBy(CREATE_TIME.asc()) + } + + queryRequest.sortType == SortType.CREATE_TIME && + queryRequest.collationType == CollationType.DESC -> { + it.orderBy(CREATE_TIME.desc()) + } + + else -> { + it.orderBy(FLOW_NO.desc()) + } + } + } + .let { + if (queryRequest.page != null && queryRequest.pageSize != null) { + val sqlLimit = PageUtil.convertPageSizeToSQLLimit(queryRequest.page, queryRequest.pageSize) + it.limit(sqlLimit.limit).offset(sqlLimit.offset) + } else { + it + } + } + .skipCheck() + .fetch() + .map { it.convert(queryRequest.memberId) } + } + } + + fun count( + dslContext: DSLContext, + queryRequest: HandoverOverviewQueryReq + ): Long { + return with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + dslContext.selectCount().from(this) + .where(buildQueryConditions(queryRequest)) + .fetchOne(0, Long::class.java)!! + } + } + + private fun buildQueryConditions( + queryRequest: HandoverOverviewQueryReq + ): List { + with(TAuthHandoverOverview.T_AUTH_HANDOVER_OVERVIEW) { + val conditions = mutableListOf() + conditions.add(APPROVER.eq(queryRequest.memberId).or(APPLICANT.eq(queryRequest.memberId))) + queryRequest.projectCode?.let { conditions.add(PROJECT_CODE.eq(queryRequest.projectCode)) } + queryRequest.projectName?.let { conditions.add(PROJECT_NAME.like("%${queryRequest.projectName}%")) } + queryRequest.title?.let { conditions.add(TITLE.like("%${queryRequest.title}%")) } + queryRequest.flowNo?.let { conditions.add(FLOW_NO.eq(queryRequest.flowNo)) } + queryRequest.flowNos?.let { conditions.add(FLOW_NO.`in`(queryRequest.flowNos)) } + queryRequest.applicant?.let { conditions.add(APPLICANT.like("%${queryRequest.applicant}%")) } + queryRequest.approver?.let { conditions.add(APPROVER.like("%${queryRequest.approver}%")) } + queryRequest.handoverStatus?.let { conditions.add(STATUS.eq(queryRequest.handoverStatus!!.value)) } + queryRequest.minCreatedTime?.let { + conditions.add( + CREATE_TIME.ge( + DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) + ) + ) + } + queryRequest.maxCreatedTime?.let { + conditions.add( + CREATE_TIME.le( + DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) + ) + ) + } + return conditions + } + } + + fun TAuthHandoverOverviewRecord.convert(memberId: String? = null): HandoverOverviewVo { + return HandoverOverviewVo( + id = id, + projectCode = projectCode, + projectName = projectName, + title = title, + flowNo = flowNo, + applicant = applicant, + approver = approver, + handoverStatus = HandoverStatus.get(status), + groupCount = groupCount, + authorizationCount = authorizationCount, + lastOperator = lastOperator, + createTime = createTime, + canRevoke = memberId?.let { memberId == applicant && status == HandoverStatus.PENDING.value }, + canApproval = memberId?.let { memberId == approver && status == HandoverStatus.PENDING.value }, + remark = remark + ) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthItsmCallbackDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthItsmCallbackDao.kt index 08ab860ecc38..08560ed68556 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthItsmCallbackDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthItsmCallbackDao.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.vo.AuthItsmCallbackInfo +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthItsmCallback import com.tencent.devops.model.auth.tables.records.TAuthItsmCallbackRecord import org.jooq.DSLContext @@ -95,6 +96,7 @@ class AuthItsmCallbackDao { return dslContext.selectFrom(this) .where(ENGLISH_NAME.eq(projectCode)) .orderBy(CREATE_TIME.desc()) + .skipCheck() .fetchAny() } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthMonitorSpaceDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthMonitorSpaceDao.kt index 6d3dcf08b208..61b59c435b0f 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthMonitorSpaceDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthMonitorSpaceDao.kt @@ -40,6 +40,19 @@ class AuthMonitorSpaceDao { } } + fun list( + dslContext: DSLContext, + projectCodes: List + ): Map { + return with(TAuthMonitorSpace.T_AUTH_MONITOR_SPACE) { + dslContext.select(PROJECT_CODE, SPACE_BIZ_ID) + .from(this) + .where(PROJECT_CODE.`in`(projectCodes)) + .fetch() + .map { Pair(it.value1(), it.value2()) }.toMap() + } + } + fun update( dslContext: DSLContext, projectCode: String, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt index 82ce971c7a30..f07e40207bdb 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt @@ -314,6 +314,19 @@ class AuthResourceDao { } } + fun listByResourceCodes( + dslContext: DSLContext, + resourceType: String, + resourceCodes: List + ): List { + return with(TAuthResource.T_AUTH_RESOURCE) { + dslContext.selectFrom(this) + .where(RESOURCE_TYPE.eq(resourceType)) + .and(RESOURCE_CODE.`in`(resourceCodes)) + .fetch().map { convert(it) } + } + } + fun getResourceCodeByType( dslContext: DSLContext, projectCode: String, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt index 73a0268d9c85..24e2d69f5196 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupConfigDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.dao +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthResourceGroupConfig import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupConfigRecord import org.jooq.DSLContext @@ -110,6 +111,7 @@ class AuthResourceGroupConfigDao { dslContext.selectFrom(this) .orderBy(CREATE_TIME.desc(), RESOURCE_TYPE, GROUP_CODE) .limit(pageSize).offset((page - 1) * pageSize) + .skipCheck() .fetch() } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt index e18db590fd82..f4a0c58ccbcd 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.AuthResourceGroup import com.tencent.devops.common.auth.api.AuthResourceType +import org.jooq.impl.DSL.count import com.tencent.devops.model.auth.tables.TAuthResourceGroup import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord import org.jooq.DSLContext @@ -204,6 +205,21 @@ class AuthResourceGroupDao { } } + fun getResourceType2Count( + dslContext: DSLContext, + projectCode: String, + iamGroupIds: List + ): Map { + return with(TAuthResourceGroup.T_AUTH_RESOURCE_GROUP) { + dslContext.select(RESOURCE_TYPE, count()) + .from(this) + .where(PROJECT_CODE.eq(projectCode)) + .and(RELATION_ID.`in`(iamGroupIds)) + .groupBy(RESOURCE_TYPE) + .fetch().map { Pair(it.value1(), it.value2().toLong()) }.toMap() + } + } + fun delete( dslContext: DSLContext, projectCode: String, @@ -368,6 +384,7 @@ class AuthResourceGroupDao { dslContext: DSLContext, projectCode: String, resourceType: String, + iamGroupIds: List? = null, offset: Int, limit: Int ): List { @@ -375,6 +392,14 @@ class AuthResourceGroupDao { val records = dslContext.selectFrom(this) .where(PROJECT_CODE.eq(projectCode)) .and(RESOURCE_TYPE.eq(resourceType)) + .let { + if (!iamGroupIds.isNullOrEmpty()) { + it.and(RELATION_ID.`in`(iamGroupIds)) + } else { + it + } + } + .orderBy(CREATE_TIME) .offset(offset) .limit(limit) .fetch() diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt index 5a016760aaf0..e8fbeab28835 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt @@ -27,11 +27,12 @@ package com.tencent.devops.auth.dao -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.common.auth.api.pojo.BkAuthGroup +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthResourceAuthorization import com.tencent.devops.model.auth.tables.TAuthResourceGroupMember import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupMemberRecord @@ -269,6 +270,29 @@ class AuthResourceGroupMemberDao { } } + fun isMemberInProject( + dslContext: DSLContext, + projectCode: String, + userId: String, + iamTemplateIds: List, + memberDeptInfos: List? + ): Boolean { + return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + dslContext.selectCount() + .from(this) + .where( + buildMemberGroupCondition( + projectCode = projectCode, + memberId = userId, + iamTemplateIds = iamTemplateIds, + memberDeptInfos = memberDeptInfos, + minExpiredAt = LocalDateTime.now() + ) + ) + .fetchOne(0, Int::class.java)!! > 0 + } + } + fun handoverGroupMembers( dslContext: DSLContext, projectCode: String, @@ -368,6 +392,7 @@ class AuthResourceGroupMemberDao { ) .orderBy(field(MEMBER_ID)) .offset(offset).limit(limit) + .skipCheck() .fetch().map { ResourceMemberInfo(id = it.value1(), name = it.value2(), type = it.value3()) } @@ -418,17 +443,17 @@ class AuthResourceGroupMemberDao { with(projectMembersQueryConditionDTO) { conditions.add(PROJECT_CODE.eq(projectCode)) if (queryTemplate == false) { - conditions.add(MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + conditions.add(MEMBER_TYPE.notEqual(MemberType.TEMPLATE.type)) } else { - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + conditions.add(MEMBER_TYPE.eq(MemberType.TEMPLATE.type)) } memberType?.let { type -> conditions.add(MEMBER_TYPE.eq(type)) } userName?.let { name -> - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.USER))) + conditions.add(MEMBER_TYPE.eq(MemberType.USER.type)) conditions.add(MEMBER_ID.like("%$name%").or(MEMBER_NAME.like("%$name%"))) } deptName?.let { name -> - conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT))) + conditions.add(MEMBER_TYPE.eq(MemberType.DEPARTMENT.type)) conditions.add(MEMBER_NAME.like("%$name%")) } minExpiredTime?.let { minTime -> conditions.add(EXPIRED_TIME.ge(minTime)) } @@ -455,6 +480,7 @@ class AuthResourceGroupMemberDao { countDistinct(field(MEMBER_ID, Long::class.java)) ).from(resourceMemberUnionAuthorizationMember) .groupBy(field(MEMBER_TYPE, Long::class.java)) + .skipCheck() .fetch().map { Pair(it.value1(), it.value2()) }.toMap() } @@ -479,6 +505,7 @@ class AuthResourceGroupMemberDao { deptName = deptName ) ) + .skipCheck() .fetchOne(0, Long::class.java) ?: 0L } @@ -494,7 +521,7 @@ class AuthResourceGroupMemberDao { ) .from(tResourceGroupMember) .where(tResourceGroupMember.PROJECT_CODE.eq(projectCode)) - .and(tResourceGroupMember.MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + .and(tResourceGroupMember.MEMBER_TYPE.notEqual(MemberType.TEMPLATE.type)) .groupBy(tResourceGroupMember.MEMBER_ID) .unionAll( dslContext.select( @@ -523,11 +550,11 @@ class AuthResourceGroupMemberDao { conditions.add(memberTypeField.eq(memberType)) } if (userName != null) { - conditions.add(memberTypeField.eq(ManagerScopesEnum.getType(ManagerScopesEnum.USER))) + conditions.add(memberTypeField.eq(MemberType.USER.type)) conditions.add(memberId.like("%$userName%").or(memberName.like("%$userName%"))) } if (deptName != null) { - conditions.add(memberTypeField.eq(ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT))) + conditions.add(memberTypeField.eq(MemberType.DEPARTMENT.type)) conditions.add(memberName.like("%$deptName%")) } return conditions @@ -536,7 +563,7 @@ class AuthResourceGroupMemberDao { /** * 获取成员按资源类型分组数量 */ - fun countMemberGroup( + fun countMemberGroupOfResourceType( dslContext: DSLContext, projectCode: String, memberId: String, @@ -544,7 +571,8 @@ class AuthResourceGroupMemberDao { resourceType: String? = null, iamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, - maxExpiredAt: LocalDateTime? = null + maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null ): Map { val conditions = buildMemberGroupCondition( projectCode = projectCode, @@ -553,7 +581,8 @@ class AuthResourceGroupMemberDao { resourceType = resourceType, iamGroupIds = iamGroupIds, minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt + maxExpiredAt = maxExpiredAt, + memberDeptInfos = memberDeptInfos ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { val select = dslContext.select(RESOURCE_TYPE, count()) @@ -564,6 +593,69 @@ class AuthResourceGroupMemberDao { } } + fun countMemberGroup( + dslContext: DSLContext, + projectCode: String, + memberId: String, + iamTemplateIds: List, + resourceType: String? = null, + iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, + minExpiredAt: LocalDateTime? = null, + maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null, + filterMemberType: MemberType? = null, + onlyExcludeUserDirectlyJoined: Boolean? = false + ): Long { + val conditions = buildMemberGroupCondition( + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + resourceType = resourceType, + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt, + memberDeptInfos = memberDeptInfos, + filterMemberType = filterMemberType + ) + val excludeConditions = buildExcludeMemberGroupCondition( + excludeIamGroupIds = excludeIamGroupIds, + onlyExcludeUserDirectlyJoined = onlyExcludeUserDirectlyJoined + ) + return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + dslContext.select(count()) + .from(this) + .where(conditions) + .let { + excludeConditions.forEach { excludeCondition -> + it.andNot(excludeCondition) + } + it + } + .fetchOne(0, Long::class.java) ?: 0L + } + } + + fun buildExcludeMemberGroupCondition( + excludeIamGroupIds: List?, + // 仅排除用户直接加入的组 + onlyExcludeUserDirectlyJoined: Boolean? + ): MutableList { + val conditions = mutableListOf() + with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + if (!excludeIamGroupIds.isNullOrEmpty()) { + // 仅排除用户直接加入的用户组 + if (onlyExcludeUserDirectlyJoined == true) { + conditions.add(IAM_GROUP_ID.`in`(excludeIamGroupIds).and(MEMBER_TYPE.eq(MemberType.USER.type))) + } else { + // 会把组织/用户/模板加入的組都排除 + conditions.add(IAM_GROUP_ID.`in`(excludeIamGroupIds)) + } + } + } + return conditions + } + fun listMemberGroupIdsInProject( dslContext: DSLContext, projectCode: String, @@ -589,11 +681,15 @@ class AuthResourceGroupMemberDao { dslContext: DSLContext, projectCode: String, memberId: String, - iamTemplateIds: List, + iamTemplateIds: List? = emptyList(), resourceType: String? = null, iamGroupIds: List? = null, + excludeIamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null, + filterMemberType: MemberType? = null, + onlyExcludeUserDirectlyJoined: Boolean? = false, offset: Int? = null, limit: Int? = null ): List { @@ -604,11 +700,23 @@ class AuthResourceGroupMemberDao { resourceType = resourceType, iamGroupIds = iamGroupIds, minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt + maxExpiredAt = maxExpiredAt, + memberDeptInfos = memberDeptInfos, + filterMemberType = filterMemberType + ) + val excludeConditions = buildExcludeMemberGroupCondition( + excludeIamGroupIds = excludeIamGroupIds, + onlyExcludeUserDirectlyJoined = onlyExcludeUserDirectlyJoined ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { dslContext.selectFrom(this) .where(conditions) + .let { + excludeConditions.forEach { excludeCondition -> + it.andNot(excludeCondition) + } + it + } .orderBy(IAM_GROUP_ID.desc()) .let { if (offset != null && limit != null) it.offset(offset).limit(limit) else it } .fetch() @@ -619,29 +727,37 @@ class AuthResourceGroupMemberDao { private fun buildMemberGroupCondition( projectCode: String, memberId: String, - iamTemplateIds: List, + iamTemplateIds: List? = emptyList(), resourceType: String? = null, iamGroupIds: List? = null, minExpiredAt: LocalDateTime? = null, - maxExpiredAt: LocalDateTime? = null + maxExpiredAt: LocalDateTime? = null, + memberDeptInfos: List? = null, + filterMemberType: MemberType? = null ): MutableList { val conditions = mutableListOf() with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { conditions.add(PROJECT_CODE.eq(projectCode)) conditions.add( + // 获取直接加入 (MEMBER_ID.eq(memberId).and( - MEMBER_TYPE.`in`( - listOf( - ManagerScopesEnum.getType(ManagerScopesEnum.USER), - ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) - ) - ) - )) - .or( - MEMBER_ID.`in`(iamTemplateIds) - .and(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) - ) - ) + MEMBER_TYPE.`in`(listOf(MemberType.USER.type, MemberType.DEPARTMENT.type)) + )).let { + // 获取模板加入 + if (!iamTemplateIds.isNullOrEmpty()) { + it.or(MEMBER_ID.`in`(iamTemplateIds).and(MEMBER_TYPE.eq(MemberType.TEMPLATE.type))) + } else { + it + } + }.let { + // 获取组织加入 + if (!memberDeptInfos.isNullOrEmpty()) { + it.or(MEMBER_ID.`in`(memberDeptInfos).and(MEMBER_TYPE.eq(MemberType.DEPARTMENT.type))) + } else { + it + } + }) + filterMemberType?.let { conditions.add(MEMBER_TYPE.eq(filterMemberType.type)) } resourceType?.let { conditions.add(RESOURCE_TYPE.eq(resourceType)) } minExpiredAt?.let { conditions.add(EXPIRED_TIME.ge(minExpiredAt)) } maxExpiredAt?.let { conditions.add(EXPIRED_TIME.le(maxExpiredAt)) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt index 4eb94a340c0b..4ca56fad06d3 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupPermissionDao.kt @@ -2,6 +2,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.dto.ResourceGroupPermissionDTO import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId import com.tencent.devops.model.auth.tables.TAuthResourceGroupPermission import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupPermissionRecord import org.jooq.Condition @@ -183,6 +184,26 @@ class AuthResourceGroupPermissionDao { } } + fun isGroupsHasProjectLevelPermission( + dslContext: DSLContext, + projectCode: String, + filterIamGroupIds: List, + actionRelatedResourceType: String, + action: String + ): Boolean { + return with(TAuthResourceGroupPermission.T_AUTH_RESOURCE_GROUP_PERMISSION) { + dslContext.selectCount() + .from(this) + .where(PROJECT_CODE.eq(projectCode)) + .and(IAM_GROUP_ID.`in`(filterIamGroupIds)) + .and(ACTION_RELATED_RESOURCE_TYPE.eq(actionRelatedResourceType)) + .and(ACTION.eq(action)) + .and(RELATED_RESOURCE_TYPE.eq(ResourceTypeId.PROJECT)) + .and(RELATED_RESOURCE_CODE.eq(projectCode)) + .fetchOne(0, Int::class.java)!! > 0 + } + } + fun listGroupResourcesWithPermission( dslContext: DSLContext, projectCode: String, @@ -194,6 +215,7 @@ class AuthResourceGroupPermissionDao { dslContext.select(RELATED_RESOURCE_TYPE, RELATED_RESOURCE_CODE) .from(this) .where(PROJECT_CODE.eq(projectCode)) + .and(IAM_GROUP_ID.`in`(filterIamGroupIds)) .and(ACTION_RELATED_RESOURCE_TYPE.eq(resourceType)) .and(ACTION.eq(action)) .groupBy(RELATED_RESOURCE_TYPE, RELATED_RESOURCE_CODE) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt index 6f1303ef2cd1..f3a715df452a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/StrategyDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.entity.StrategyInfo +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.auth.tables.TAuthStrategy import com.tencent.devops.model.auth.tables.records.TAuthStrategyRecord import org.jooq.DSLContext @@ -39,7 +40,8 @@ class StrategyDao { fun create(dslContext: DSLContext, userId: String, strategyInfo: StrategyInfo): Int { with(TAuthStrategy.T_AUTH_STRATEGY) { - return dslContext.insertInto(this, + return dslContext.insertInto( + this, STRATEGY_NAME, STRATEGY_BODY, IS_DELETE, @@ -86,7 +88,11 @@ class StrategyDao { fun list(dslContext: DSLContext): Result? { with(TAuthStrategy.T_AUTH_STRATEGY) { - return dslContext.selectFrom(this).where((IS_DELETE.eq(0))).orderBy(CREATE_TIME.desc()).fetch() + return dslContext.selectFrom(this) + .where((IS_DELETE.eq(0))) + .orderBy(CREATE_TIME.desc()) + .skipCheck() + .fetch() } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt index aa62e4ef5826..8f0b51098939 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacAuthConfiguration.kt @@ -43,6 +43,9 @@ import com.tencent.bk.sdk.iam.service.v2.impl.V2GrantServiceImpl import com.tencent.bk.sdk.iam.service.v2.impl.V2ManagerServiceImpl import com.tencent.bk.sdk.iam.service.v2.impl.V2PolicyServiceImpl import com.tencent.devops.auth.dao.AuthActionDao +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthHandoverDetailDao +import com.tencent.devops.auth.dao.AuthHandoverOverviewDao import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthMonitorSpaceDao import com.tencent.devops.auth.dao.AuthResourceDao @@ -57,15 +60,16 @@ import com.tencent.devops.auth.provider.rbac.service.AuthResourceService import com.tencent.devops.auth.provider.rbac.service.ItsmService import com.tencent.devops.auth.provider.rbac.service.PermissionGradeManagerService import com.tencent.devops.auth.provider.rbac.service.PermissionSubsetManagerService -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionApplyService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionAuthMonitorSpaceService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionAuthorizationScopesService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionExtService +import com.tencent.devops.auth.provider.rbac.service.RbacPermissionHandoverApplicationService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionItsmCallbackService +import com.tencent.devops.auth.provider.rbac.service.RbacPermissionManageFacadeServiceImpl import com.tencent.devops.auth.provider.rbac.service.RbacPermissionProjectService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceCallbackService -import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupAndMemberFacadeServiceImpl import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupPermissionService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceGroupSyncService @@ -93,7 +97,8 @@ import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.ResourceService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.MigrateCreatorFixService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService @@ -198,7 +203,7 @@ class RbacAuthConfiguration { ) @Bean - fun permissionResourceGroupAndMemberFacadeService( + fun permissionFacadeService( permissionResourceGroupService: PermissionResourceGroupService, groupPermissionService: PermissionResourceGroupPermissionService, permissionResourceMemberService: PermissionResourceMemberService, @@ -207,8 +212,17 @@ class RbacAuthConfiguration { dslContext: DSLContext, deptService: DeptService, iamV2ManagerService: V2ManagerService, - rbacCacheService: RbacCacheService - ) = RbacPermissionResourceGroupAndMemberFacadeServiceImpl( + permissionAuthorizationService: PermissionAuthorizationService, + syncIamGroupMemberService: PermissionResourceGroupSyncService, + authAuthorizationDao: AuthAuthorizationDao, + permissionHandoverApplicationService: PermissionHandoverApplicationService, + rbacCommonService: RbacCommonService, + redisOperation: RedisOperation, + authorizationDao: AuthAuthorizationDao, + authResourceService: AuthResourceService, + client: Client, + config: CommonConfig + ) = RbacPermissionManageFacadeServiceImpl( permissionResourceGroupService = permissionResourceGroupService, groupPermissionService = groupPermissionService, permissionResourceMemberService = permissionResourceMemberService, @@ -217,13 +231,22 @@ class RbacAuthConfiguration { dslContext = dslContext, deptService = deptService, iamV2ManagerService = iamV2ManagerService, - rbacCacheService = rbacCacheService + permissionAuthorizationService = permissionAuthorizationService, + syncIamGroupMemberService = syncIamGroupMemberService, + authAuthorizationDao = authAuthorizationDao, + permissionHandoverApplicationService = permissionHandoverApplicationService, + rbacCommonService = rbacCommonService, + redisOperation = redisOperation, + authorizationDao = authorizationDao, + authResourceService = authResourceService, + client = client, + config = config ) @Bean fun permissionResourceGroupPermissionService( v2ManagerService: V2ManagerService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, monitorSpaceService: AuthMonitorSpaceService, authResourceGroupDao: AuthResourceGroupDao, dslContext: DSLContext, @@ -237,7 +260,7 @@ class RbacAuthConfiguration { objectMapper: ObjectMapper ) = RbacPermissionResourceGroupPermissionService( v2ManagerService = v2ManagerService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, monitorSpaceService = monitorSpaceService, authResourceGroupDao = authResourceGroupDao, dslContext = dslContext, @@ -259,18 +282,14 @@ class RbacAuthConfiguration { authResourceGroupMemberDao: AuthResourceGroupMemberDao, dslContext: DSLContext, deptService: DeptService, - rbacCacheService: RbacCacheService, - permissionAuthorizationService: PermissionAuthorizationService, - syncIamGroupMemberService: PermissionResourceGroupSyncService + rbacCommonService: RbacCommonService ) = RbacPermissionResourceMemberService( authResourceService = authResourceService, iamV2ManagerService = iamV2ManagerService, authResourceGroupDao = authResourceGroupDao, authResourceGroupMemberDao = authResourceGroupMemberDao, dslContext = dslContext, - deptService = deptService, - permissionAuthorizationService = permissionAuthorizationService, - syncIamGroupMemberService = syncIamGroupMemberService + deptService = deptService ) @Bean @@ -300,7 +319,7 @@ class RbacAuthConfiguration { iamV2PolicyService: PolicyService, authResourceCodeConverter: AuthResourceCodeConverter, superManagerService: SuperManagerService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, client: Client, authProjectUserMetricsService: AuthProjectUserMetricsService ) = RbacPermissionService( @@ -310,7 +329,7 @@ class RbacAuthConfiguration { policyService = iamV2PolicyService, authResourceCodeConverter = authResourceCodeConverter, superManagerService = superManagerService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, client = client, authProjectUserMetricsService = authProjectUserMetricsService ) @@ -322,19 +341,21 @@ class RbacAuthConfiguration { authResourceService: AuthResourceService, authResourceGroupDao: AuthResourceGroupDao, dslContext: DSLContext, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, resourceGroupMemberService: RbacPermissionResourceMemberService, client: Client, - resourceMemberService: PermissionResourceMemberService + resourceMemberService: PermissionResourceMemberService, + permissionManageFacadeService: PermissionManageFacadeService ) = RbacPermissionProjectService( authHelper = authHelper, authResourceService = authResourceService, authResourceGroupDao = authResourceGroupDao, dslContext = dslContext, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, resourceGroupMemberService = resourceGroupMemberService, client = client, - resourceMemberService = resourceMemberService + resourceMemberService = resourceMemberService, + permissionManageFacadeService = permissionManageFacadeService ) @Bean @@ -355,7 +376,7 @@ class RbacAuthConfiguration { authResourceService: AuthResourceService, authResourceGroupConfigDao: AuthResourceGroupConfigDao, authResourceGroupDao: AuthResourceGroupDao, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, config: CommonConfig, client: Client, authResourceCodeConverter: AuthResourceCodeConverter, @@ -369,7 +390,7 @@ class RbacAuthConfiguration { authResourceService = authResourceService, authResourceGroupConfigDao = authResourceGroupConfigDao, authResourceGroupDao = authResourceGroupDao, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, config = config, client = client, authResourceCodeConverter = authResourceCodeConverter, @@ -383,12 +404,16 @@ class RbacAuthConfiguration { @Primary fun rbacPermissionResourceValidateService( permissionService: PermissionService, - rbacCacheService: RbacCacheService, - client: Client + rbacCommonService: RbacCommonService, + client: Client, + authAuthorizationDao: AuthAuthorizationDao, + dslContext: DSLContext ) = RbacPermissionResourceValidateService( permissionService = permissionService, - rbacCacheService = rbacCacheService, - client = client + rbacCommonService = rbacCommonService, + client = client, + authAuthorizationDao = authAuthorizationDao, + dslContext = dslContext ) @Bean @@ -401,7 +426,7 @@ class RbacAuthConfiguration { @Bean fun migrateResourceService( resourceService: ResourceService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, rbacPermissionResourceService: RbacPermissionResourceService, migrateCreatorFixService: MigrateCreatorFixService, authResourceService: AuthResourceService, @@ -416,7 +441,7 @@ class RbacAuthConfiguration { authResourceGroupDao: AuthResourceGroupDao ) = MigrateResourceService( resourceService = resourceService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, rbacPermissionResourceService = rbacPermissionResourceService, migrateCreatorFixService = migrateCreatorFixService, authResourceService = authResourceService, @@ -450,7 +475,7 @@ class RbacAuthConfiguration { @Bean fun migrateResultService( permissionService: PermissionService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, migrateResourceCodeConverter: MigrateResourceCodeConverter, authVerifyRecordService: AuthVerifyRecordService, migrateResourceService: MigrateResourceService, @@ -462,7 +487,7 @@ class RbacAuthConfiguration { redisOperation: RedisOperation ) = MigrateResultService( permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, migrateResourceCodeConverter = migrateResourceCodeConverter, authVerifyRecordService = authVerifyRecordService, migrateResourceService = migrateResourceService, @@ -485,7 +510,7 @@ class RbacAuthConfiguration { migrateIamApiService: MigrateIamApiService, authResourceCodeConverter: AuthResourceCodeConverter, permissionService: PermissionService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, authMigrationDao: AuthMigrationDao, deptService: DeptService, permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, @@ -500,7 +525,7 @@ class RbacAuthConfiguration { migrateResourceCodeConverter = migrateResourceCodeConverter, authResourceCodeConverter = authResourceCodeConverter, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, authMigrationDao = authMigrationDao, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, @@ -518,7 +543,7 @@ class RbacAuthConfiguration { migrateIamApiService: MigrateIamApiService, authResourceCodeConverter: AuthResourceCodeConverter, permissionService: PermissionService, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, authMigrationDao: AuthMigrationDao, deptService: DeptService, permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, @@ -533,7 +558,7 @@ class RbacAuthConfiguration { migrateResourceCodeConverter = migrateResourceCodeConverter, authResourceCodeConverter = authResourceCodeConverter, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, authMigrationDao = authMigrationDao, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, @@ -556,7 +581,6 @@ class RbacAuthConfiguration { dslContext: DSLContext, authMigrationDao: AuthMigrationDao, authMonitorSpaceDao: AuthMonitorSpaceDao, - cacheService: RbacCacheService, permissionResourceMemberService: RbacPermissionResourceMemberService, migrateResourceAuthorizationService: MigrateResourceAuthorizationService, migrateResourceGroupService: MigrateResourceGroupService @@ -574,7 +598,6 @@ class RbacAuthConfiguration { dslContext = dslContext, authMigrationDao = authMigrationDao, authMonitorSpaceDao = authMonitorSpaceDao, - cacheService = cacheService, permissionResourceMemberService = permissionResourceMemberService, migrateResourceAuthorizationService = migrateResourceAuthorizationService, migrateResourceGroupService = migrateResourceGroupService @@ -590,13 +613,38 @@ class RbacAuthConfiguration { authResourceService: AuthResourceService, authResourceGroupMemberService: PermissionResourceMemberService, dslContext: DSLContext, - resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + permissionManageFacadeService: PermissionManageFacadeService ) = MigratePermissionHandoverService( permissionResourceMemberService = permissionResourceMemberService, authResourceGroupDao = authResourceGroupDao, authResourceService = authResourceService, dslContext = dslContext, - resourceGroupAndMemberFacadeService = resourceGroupAndMemberFacadeService + permissionManageFacadeService = permissionManageFacadeService + ) + + @Bean + fun permissionHandoverService( + dslContext: DSLContext, + handoverOverviewDao: AuthHandoverOverviewDao, + handoverDetailDao: AuthHandoverDetailDao, + authorizationDao: AuthAuthorizationDao, + authResourceGroupDao: AuthResourceGroupDao, + rbacCommonService: RbacCommonService, + redisOperation: RedisOperation, + client: Client, + config: CommonConfig, + deptService: DeptService + ) = RbacPermissionHandoverApplicationService( + dslContext = dslContext, + handoverOverviewDao = handoverOverviewDao, + handoverDetailDao = handoverDetailDao, + authorizationDao = authorizationDao, + authResourceGroupDao = authResourceGroupDao, + rbacCommonService = rbacCommonService, + redisOperation = redisOperation, + client = client, + config = config, + deptService = deptService ) @Bean @@ -635,11 +683,12 @@ class RbacAuthConfiguration { authResourceGroupDao: AuthResourceGroupDao, iamV2ManagerService: V2ManagerService, authResourceGroupMemberDao: AuthResourceGroupMemberDao, - rbacCacheService: RbacCacheService, + rbacCommonService: RbacCommonService, redisOperation: RedisOperation, authResourceSyncDao: AuthResourceSyncDao, authResourceGroupApplyDao: AuthResourceGroupApplyDao, - resourceGroupPermissionService: PermissionResourceGroupPermissionService + resourceGroupPermissionService: PermissionResourceGroupPermissionService, + deptService: DeptService ) = RbacPermissionResourceGroupSyncService( client = client, dslContext = dslContext, @@ -647,10 +696,11 @@ class RbacAuthConfiguration { authResourceGroupDao = authResourceGroupDao, iamV2ManagerService = iamV2ManagerService, authResourceGroupMemberDao = authResourceGroupMemberDao, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, redisOperation = redisOperation, authResourceSyncDao = authResourceSyncDao, authResourceGroupApplyDao = authResourceGroupApplyDao, - resourceGroupPermissionService = resourceGroupPermissionService + resourceGroupPermissionService = resourceGroupPermissionService, + deptService = deptService ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacServiceConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacServiceConfiguration.kt index 47d4f23c856b..647ab1149e8a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacServiceConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/config/RbacServiceConfiguration.kt @@ -45,7 +45,7 @@ import com.tencent.devops.auth.provider.rbac.service.AuthResourceService import com.tencent.devops.auth.provider.rbac.service.ItsmService import com.tencent.devops.auth.provider.rbac.service.PermissionGradeManagerService import com.tencent.devops.auth.provider.rbac.service.PermissionSubsetManagerService -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.service.AuthAuthorizationScopesService import com.tencent.devops.auth.service.AuthProjectUserMetricsService import com.tencent.devops.auth.service.BkHttpRequestService @@ -72,7 +72,7 @@ class RbacServiceConfiguration { iamConfiguration: IamConfiguration, authResourceGroupConfigDao: AuthResourceGroupConfigDao, authProjectUserMetricsService: AuthProjectUserMetricsService - ) = RbacCacheService( + ) = RbacCommonService( dslContext = dslContext, authResourceTypeDao = authResourceTypeDao, authActionDao = authActionDao, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCommonService.kt similarity index 80% rename from src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt rename to src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCommonService.kt index 1a9f467e7b7d..e3126357fbcd 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCommonService.kt @@ -3,18 +3,21 @@ package com.tencent.devops.auth.provider.rbac.service import com.fasterxml.jackson.core.type.TypeReference import com.github.benmanes.caffeine.cache.Caffeine import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.SubjectDTO import com.tencent.bk.sdk.iam.dto.V2QueryPolicyDTO import com.tencent.bk.sdk.iam.dto.action.ActionDTO import com.tencent.bk.sdk.iam.dto.resource.V2ResourceNode import com.tencent.bk.sdk.iam.service.PolicyService +import com.tencent.devops.auth.constant.AuthI18nConstants import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthActionDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceTypeDao import com.tencent.devops.auth.pojo.AuthGroupConfigAction +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.pojo.vo.ActionInfoVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.auth.service.AuthProjectUserMetricsService import com.tencent.devops.common.api.exception.ErrorCodeException @@ -22,12 +25,13 @@ import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.rbac.utils.RbacAuthUtils +import com.tencent.devops.common.web.utils.I18nUtil import org.jooq.DSLContext import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit @Suppress("MagicNumber", "LongParameterList") -class RbacCacheService constructor( +class RbacCommonService( private val dslContext: DSLContext, private val authResourceTypeDao: AuthResourceTypeDao, private val authActionDao: AuthActionDao, @@ -38,7 +42,7 @@ class RbacCacheService constructor( ) { companion object { - private val logger = LoggerFactory.getLogger(RbacCacheService::class.java) + private val logger = LoggerFactory.getLogger(RbacCommonService::class.java) } /*获取资源类型下的动作*/ @@ -190,7 +194,7 @@ class RbacCacheService constructor( val subject = SubjectDTO.builder() .id(userId) - .type(ManagerScopesEnum.getType(ManagerScopesEnum.USER)) + .type(MemberType.USER.type) .build() val queryPolicyDTO = V2QueryPolicyDTO.builder().system(iamConfiguration.systemId) .subject(subject) @@ -213,4 +217,42 @@ class RbacCacheService constructor( ) } } + + fun convertResourceType2Count( + resourceType2Count: Map, + type: HandoverType = HandoverType.GROUP + ): List { + val memberGroupCountList = mutableListOf() + // 项目排在第一位 + resourceType2Count[AuthResourceType.PROJECT.value]?.let { projectCount -> + memberGroupCountList.add( + ResourceType2CountVo( + resourceType = AuthResourceType.PROJECT.value, + resourceTypeName = I18nUtil.getCodeLanMessage( + messageCode = AuthResourceType.PROJECT.value + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX + ), + count = projectCount, + type = type + ) + ) + } + + listResourceTypes() + .filter { it.resourceType != AuthResourceType.PROJECT.value } + .forEach { resourceTypeInfoVo -> + resourceType2Count[resourceTypeInfoVo.resourceType]?.let { count -> + val memberGroupCount = ResourceType2CountVo( + resourceType = resourceTypeInfoVo.resourceType, + resourceTypeName = I18nUtil.getCodeLanMessage( + messageCode = resourceTypeInfoVo.resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, + defaultMessage = resourceTypeInfoVo.name + ), + count = count, + type = type + ) + memberGroupCountList.add(memberGroupCount) + } + } + return memberGroupCountList + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionApplyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionApplyService.kt index 63e94d7445bb..7be37a4a987a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionApplyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionApplyService.kt @@ -60,7 +60,7 @@ class RbacPermissionApplyService @Autowired constructor( val authResourceService: AuthResourceService, val authResourceGroupConfigDao: AuthResourceGroupConfigDao, val authResourceGroupDao: AuthResourceGroupDao, - val rbacCacheService: RbacCacheService, + val rbacCommonService: RbacCommonService, val config: CommonConfig, val client: Client, val authResourceCodeConverter: AuthResourceCodeConverter, @@ -80,11 +80,11 @@ class RbacPermissionApplyService @Autowired constructor( private val codeccTaskDetailRedirectUri = "${config.devopsHostGateway}/console/codecc/%s/task/%s/detail?buildNum=latest" private val groupPermissionDetailRedirectUri = "${config.devopsHostGateway}/permission/group/detail?group_id=%s&x-devops-project-id=%s" override fun listResourceTypes(userId: String): List { - return rbacCacheService.listResourceTypes() + return rbacCommonService.listResourceTypes() } override fun listActions(userId: String, resourceType: String): List { - return rbacCacheService.listResourceType2Action(resourceType) + return rbacCommonService.listResourceType2Action(resourceType) } override fun listGroupsForApply( @@ -263,7 +263,7 @@ class RbacPermissionApplyService @Autowired constructor( return managerRoleGroupInfoList.map { gInfo -> val dbGroupRecord = dbGroupRecords.find { record -> record.relationId == gInfo.id.toString() } val resourceType = dbGroupRecord?.resourceType ?: AuthResourceType.PROJECT.value - val resourceTypeName = rbacCacheService.getResourceTypeInfo(resourceType).name + val resourceTypeName = rbacCommonService.getResourceTypeInfo(resourceType).name val resourceName = dbGroupRecord?.resourceName ?: projectName val resourceCode = dbGroupRecord?.resourceCode ?: projectId val memberJoinedResult = verifyMemberJoinedResult[gInfo.id.toInt()] @@ -326,7 +326,7 @@ class RbacPermissionApplyService @Autowired constructor( itsmService.buildGroupApplyItsmValue( ApplyJoinGroupFormDataInfo( projectName = projectInfo.projectName, - resourceTypeName = rbacCacheService.getResourceTypeInfo(resourceGroupInfo.resourceType).name, + resourceTypeName = rbacCommonService.getResourceTypeInfo(resourceGroupInfo.resourceType).name, resourceName = resourceGroupInfo.resourceName, groupName = resourceGroupInfo.groupName, validityPeriod = generateValidityPeriod(applyJoinGroupInfo.expiredAt.toLong()), @@ -487,11 +487,11 @@ class RbacPermissionApplyService @Autowired constructor( ) val groupInfoList: MutableList = mutableListOf() // 判断action是否为空 - val actionInfo = if (action != null) rbacCacheService.getActionInfo(action) else null + val actionInfo = if (action != null) rbacCommonService.getActionInfo(action) else null val iamRelatedResourceType = actionInfo?.relatedResourceType ?: resourceType val resourceTypeName = I18nUtil.getCodeLanMessage( messageCode = resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, - defaultMessage = rbacCacheService.getResourceTypeInfo(resourceType).name + defaultMessage = rbacCommonService.getResourceTypeInfo(resourceType).name ) val projectInfo = authResourceService.get( @@ -578,7 +578,7 @@ class RbacPermissionApplyService @Autowired constructor( ) } else { if (isEnablePermission) { - rbacCacheService.getGroupConfigAction(finalResourceType).forEach { + rbacCommonService.getGroupConfigAction(finalResourceType).forEach { if (it.actions.contains(action)) { buildRedirectGroupInfo( groupInfoList = groupInfoList, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionAuthMonitorSpaceService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionAuthMonitorSpaceService.kt index f194d1cefede..c6d316f6db02 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionAuthMonitorSpaceService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionAuthMonitorSpaceService.kt @@ -143,6 +143,13 @@ class RbacPermissionAuthMonitorSpaceService constructor( return "-${dbMonitorSpaceRecord.spaceBizId}" } + override fun listMonitorSpaceBizIds(projectCode: List): Map { + return authMonitorSpaceDao.list( + dslContext = dslContext, + projectCodes = projectCode + ).mapValues { "-${it.value}" } + } + private fun updateMonitorSpace( projectCode: String, monitorSpaceUpdateInfo: MonitorSpaceUpdateInfo, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt new file mode 100644 index 000000000000..fee1d2e7bd89 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionHandoverApplicationService.kt @@ -0,0 +1,368 @@ +package com.tencent.devops.auth.provider.rbac.service + +import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum +import com.tencent.devops.auth.constant.AuthI18nConstants.BK_APPLY_TO_HANDOVER +import com.tencent.devops.auth.constant.AuthI18nConstants.BK_HANDOVER_AUTHORIZATIONS +import com.tencent.devops.auth.constant.AuthI18nConstants.BK_HANDOVER_GROUPS +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthHandoverDetailDao +import com.tencent.devops.auth.dao.AuthHandoverOverviewDao +import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest +import com.tencent.devops.common.client.Client +import com.tencent.devops.common.notify.enums.NotifyType +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.config.CommonConfig +import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.notify.api.service.ServiceNotifyMessageTemplateResource +import com.tencent.devops.notify.pojo.SendNotifyMessageTemplateRequest +import org.jooq.DSLContext +import org.jooq.impl.DSL +import org.slf4j.LoggerFactory +import java.time.LocalDateTime + +@Suppress("ALL") +class RbacPermissionHandoverApplicationService( + private val dslContext: DSLContext, + private val handoverOverviewDao: AuthHandoverOverviewDao, + private val handoverDetailDao: AuthHandoverDetailDao, + private val authorizationDao: AuthAuthorizationDao, + private val authResourceGroupDao: AuthResourceGroupDao, + private val rbacCommonService: RbacCommonService, + private val redisOperation: RedisOperation, + private val client: Client, + private val config: CommonConfig, + private val deptService: DeptService +) : PermissionHandoverApplicationService { + override fun createHandoverApplication( + overview: HandoverOverviewCreateDTO, + details: List + ): String { + logger.info("create handover application:{}|{}", overview, details) + val flowNo = generateFlowNo() + val (title, handoverOverviewContentOfEmail, handoverOverviewContentOfRtx) = + generateOverviewContent( + groupCount = overview.groupCount, + authorizationCount = overview.authorizationCount + ) + dslContext.transaction { configuration -> + val transactionContext = DSL.using(configuration) + handoverOverviewDao.create( + dslContext = transactionContext, + overviewDTO = overview.copy( + flowNo = flowNo, + title = title + ) + ) + handoverDetailDao.batchCreate( + dslContext = transactionContext, + handoverDetailDTOs = details.map { it.copy(flowNo = flowNo) } + ) + } + val handoverFromCnName = deptService.getMemberInfo(overview.applicant, ManagerScopesEnum.USER).displayName + val handoverToCnName = deptService.getMemberInfo(overview.approver, ManagerScopesEnum.USER).displayName + val resourceType2CountOfHandover = getResourceType2CountOfHandoverApplication(flowNo) + val handoverOverviewTableBuilder = StringBuilder() + resourceType2CountOfHandover.forEach { + handoverOverviewTableBuilder.append( + String.format( + HANDOVER_APPLICATION_TABLE_OF_EMAIL, it.type.alias, it.resourceTypeName, it.count + ) + ) + } + val handoverOverviewTable = handoverOverviewTableBuilder.toString() + val bodyParams = mapOf( + "handoverFrom" to overview.applicant.plus("($handoverFromCnName)"), + "handoverTo" to overview.approver.plus("($handoverToCnName)"), + "projectName" to overview.projectName, + "handoverOverviews" to handoverOverviewContentOfEmail, + "handoverOverviewContentOfRtx" to handoverOverviewContentOfRtx, + "table" to handoverOverviewTable, + "url" to String.format(handoverApplicationUrl, flowNo) + ) + logger.info("send handover application email:{} ", bodyParams) + val request = SendNotifyMessageTemplateRequest( + templateCode = HANDOVER_APPLICATION_TEMPLATE_CODE, + bodyParams = bodyParams, + titleParams = bodyParams, + notifyType = mutableSetOf(NotifyType.WEWORK.name, NotifyType.EMAIL.name), + receivers = mutableSetOf(overview.approver) + ) + kotlin.runCatching { + client.get(ServiceNotifyMessageTemplateResource::class).sendNotifyMessageByTemplate(request) + }.onFailure { + logger.warn("notify email fail ${it.message}|$bodyParams|${overview.approver}") + } + return flowNo + } + + private fun generateOverviewContent( + groupCount: Int, + authorizationCount: Int + ): Triple { + val bkHandoverGroups = I18nUtil.getCodeLanMessage(BK_HANDOVER_GROUPS) + val bkHandoverAuthorizations = I18nUtil.getCodeLanMessage(BK_HANDOVER_AUTHORIZATIONS) + var titleOfApplication = I18nUtil.getCodeLanMessage(BK_APPLY_TO_HANDOVER) + var handoverOverviewContentOfEmail = "" + var handoverOverviewContentOfRtx = "" + when { + groupCount > 0 && authorizationCount > 0 -> { + titleOfApplication = titleOfApplication.plus(" $groupCount ").plus( + bkHandoverGroups.plus(",").plus(" $authorizationCount ").plus(bkHandoverAuthorizations) + ) + handoverOverviewContentOfEmail = """ $groupCount $bkHandoverGroups, $authorizationCount $bkHandoverAuthorizations""".trimMargin() + handoverOverviewContentOfRtx = handoverOverviewContentOfRtx.plus(groupCount).plus( + bkHandoverGroups.plus(",").plus(authorizationCount).plus(bkHandoverAuthorizations) + ) + } + + groupCount > 0 -> { + titleOfApplication = titleOfApplication.plus(" $groupCount ").plus(bkHandoverGroups) + handoverOverviewContentOfEmail = """ $groupCount $bkHandoverGroups""".trimMargin() + handoverOverviewContentOfRtx = handoverOverviewContentOfRtx.plus(groupCount).plus(bkHandoverGroups) + } + + else -> { + titleOfApplication = titleOfApplication.plus(" $authorizationCount ").plus(bkHandoverAuthorizations) + handoverOverviewContentOfEmail = """ $authorizationCount $bkHandoverAuthorizations""".trimMargin() + handoverOverviewContentOfRtx = handoverOverviewContentOfRtx.plus(authorizationCount).plus(bkHandoverAuthorizations) + } + } + return Triple(titleOfApplication, handoverOverviewContentOfEmail, handoverOverviewContentOfRtx) + } + + /** + * 生成格式如 REQ2024111300001 + * REQ 固定前缀 + * 20241113 表示日期 + * 00001 表示当天第几个单号 + * */ + override fun generateFlowNo(): String { + val currentTime = DateTimeUtil.toDateTime(LocalDateTime.now(), DateTimeUtil.YYYYMMDD) + val key = String.format(FLOW_NO_KEY, currentTime) + val incrementedValue = redisOperation.increment(key, 1) + redisOperation.expire(key, 3600 * 24) + val formattedIncrementedValue = String.format("%05d", incrementedValue) + return FLOW_NO_PREFIX + currentTime + formattedIncrementedValue + } + + override fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) { + logger.info("update handover application:{}", overview) + handoverOverviewDao.update( + dslContext = dslContext, + overviewDTO = overview + ) + } + + override fun getHandoverOverview(flowNo: String): HandoverOverviewVo { + return handoverOverviewDao.get( + dslContext = dslContext, + flowNo = flowNo + ) ?: throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_HANDOVER_OVERVIEW_NOT_EXIST + ) + } + + override fun listHandoverOverviews( + queryRequest: HandoverOverviewQueryReq + ): SQLPage { + logger.info("list handover overviews :$queryRequest") + val records = handoverOverviewDao.list( + dslContext = dslContext, + queryRequest = queryRequest + ) + val count = handoverOverviewDao.count( + dslContext = dslContext, + queryRequest = queryRequest + ) + return SQLPage( + records = records, + count = count + ) + } + + override fun listAuthorizationsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + logger.info("list authorizations of handover application :$queryReq") + val flowNo = queryReq.flowNo!! + val overview = getHandoverOverview(flowNo) + val resourceCodes = handoverDetailDao.list( + dslContext = dslContext, + projectCode = overview.projectCode, + flowNos = listOf(flowNo), + resourceType = queryReq.resourceType, + handoverType = HandoverType.AUTHORIZATION + ).map { it.itemId } + val count = handoverDetailDao.count( + dslContext = dslContext, + projectCode = overview.projectCode, + flowNos = listOf(flowNo), + resourceType = queryReq.resourceType, + handoverType = HandoverType.AUTHORIZATION + ) + val records = authorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = overview.projectCode, + resourceType = queryReq.resourceType, + filterResourceCodes = resourceCodes, + page = queryReq.page, + pageSize = queryReq.pageSize + ) + ).map { + HandoverAuthorizationDetailVo( + resourceCode = it.resourceCode, + resourceName = it.resourceName, + handoverType = HandoverType.AUTHORIZATION, + handoverFrom = overview.applicant + ) + } + return SQLPage(records = records, count = count) + } + + override fun listGroupsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + logger.info("list groups of handover application :$queryReq") + val flowNo = queryReq.flowNo!! + val handoverOverview = getHandoverOverview(flowNo) + val iamGroupIdsByHandover = listHandoverDetails( + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + resourceType = queryReq.resourceType, + handoverType = HandoverType.GROUP + ).map { it.itemId } + if (iamGroupIdsByHandover.isEmpty()) + return SQLPage(0, emptyList()) + val convertPageSizeToSQLLimit = PageUtil.convertPageSizeToSQLLimit( + page = queryReq.page, + pageSize = queryReq.pageSize + ) + val records = authResourceGroupDao.listGroupByResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + resourceType = queryReq.resourceType, + iamGroupIds = iamGroupIdsByHandover, + offset = convertPageSizeToSQLLimit.offset, + limit = convertPageSizeToSQLLimit.limit + ).map { + HandoverGroupDetailVo( + projectCode = it.projectCode, + iamGroupId = it.relationId, + groupName = it.groupName, + groupDesc = it.description, + resourceCode = it.resourceCode, + resourceName = it.resourceName + ) + } + return SQLPage( + count = iamGroupIdsByHandover.size.toLong(), + records = records + ) + } + + override fun getResourceType2CountOfHandoverApplication(flowNo: String): List { + logger.info("get resource type count of handover application:$flowNo") + val handoverOverview = getHandoverOverview(flowNo) + val resourceType2CountWithGroup = handoverDetailDao.countWithResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + handoverType = HandoverType.GROUP + ) + val resourceType2CountWithAuthorization = handoverDetailDao.countWithResourceType( + dslContext = dslContext, + projectCode = handoverOverview.projectCode, + flowNo = flowNo, + handoverType = HandoverType.AUTHORIZATION + ) + val result = mutableListOf() + if (resourceType2CountWithGroup.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = resourceType2CountWithGroup, + type = HandoverType.GROUP + ) + ) + } + if (resourceType2CountWithAuthorization.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = resourceType2CountWithAuthorization, + type = HandoverType.AUTHORIZATION + ) + ) + } + return result + } + + override fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String?, + handoverType: HandoverType? + ): List { + return handoverDetailDao.list( + dslContext = dslContext, + projectCode = projectCode, + flowNos = listOf(flowNo), + resourceType = resourceType, + handoverType = handoverType + ) + } + + override fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? + ): List { + logger.info("list member handover details:$projectCode|$memberId|$handoverType|$resourceType") + val handoverOverviews = listHandoverOverviews( + queryRequest = HandoverOverviewQueryReq( + memberId = memberId, + projectCode = projectCode, + applicant = memberId, + handoverStatus = HandoverStatus.PENDING + ) + ).records + val flowNos = handoverOverviews.map { it.flowNo } + val flowNo2Approver = handoverOverviews.associate { Pair(it.flowNo, it.approver) } + return handoverDetailDao.list( + dslContext = dslContext, + projectCode = projectCode, + flowNos = flowNos, + resourceType = resourceType, + handoverType = handoverType + ).map { it.copy(approver = flowNo2Approver[it.flowNo]) } + } + + private val handoverApplicationUrl = "${config.devopsHostGateway}/console/permission/my-handover?type=handoverToMe&flowNo=%s" + + companion object { + private val logger = LoggerFactory.getLogger(RbacPermissionHandoverApplicationService::class.java) + private const val FLOW_NO_PREFIX = "REQ" + private const val FLOW_NO_KEY = "AUTH:HANDOVER:FLOW:NO:%s" + private const val HANDOVER_APPLICATION_TABLE_OF_EMAIL = "%s%s%s" + private const val HANDOVER_APPLICATION_TEMPLATE_CODE = "BK_PERMISSIONS_HANDOVER_APPLICATION" + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt new file mode 100644 index 000000000000..c8d6608d9f5f --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionManageFacadeServiceImpl.kt @@ -0,0 +1,2399 @@ +package com.tencent.devops.auth.provider.rbac.service + +import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum +import com.tencent.bk.sdk.iam.dto.manager.ManagerMember +import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse +import com.tencent.bk.sdk.iam.service.v2.V2ManagerService +import com.tencent.devops.auth.constant.AuthI18nConstants +import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_APPROVAL +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_FINISH +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_HANDLE +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_HANDOVER_REVOKE +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_SINGLE_GROUP_REMOVE +import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO +import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.HandoverAction +import com.tencent.devops.auth.pojo.enum.HandoverQueryChannel +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.JoinedType +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewBatchUpdateReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.DeptService +import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupService +import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService +import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.lock.HandleHandoverApplicationLock +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.api.util.timestamp +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.auth.api.ActionId +import com.tencent.devops.common.auth.api.AuthPermission +import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId +import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import com.tencent.devops.common.auth.enums.HandoverChannelCode +import com.tencent.devops.common.client.Client +import com.tencent.devops.common.notify.enums.NotifyType +import com.tencent.devops.common.redis.RedisOperation +import com.tencent.devops.common.service.config.CommonConfig +import com.tencent.devops.common.service.utils.RetryUtils +import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord +import com.tencent.devops.notify.api.service.ServiceNotifyMessageTemplateResource +import com.tencent.devops.notify.pojo.SendNotifyMessageTemplateRequest +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import java.time.LocalDateTime +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +@Suppress("ComplexCondition") +class RbacPermissionManageFacadeServiceImpl( + private val permissionResourceGroupService: PermissionResourceGroupService, + private val groupPermissionService: PermissionResourceGroupPermissionService, + private val permissionResourceMemberService: PermissionResourceMemberService, + private val authResourceGroupDao: AuthResourceGroupDao, + private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, + private val dslContext: DSLContext, + private val deptService: DeptService, + private val iamV2ManagerService: V2ManagerService, + private val authAuthorizationDao: AuthAuthorizationDao, + private val syncIamGroupMemberService: PermissionResourceGroupSyncService, + private val permissionAuthorizationService: PermissionAuthorizationService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService, + private val rbacCommonService: RbacCommonService, + private val redisOperation: RedisOperation, + private val authorizationDao: AuthAuthorizationDao, + private val authResourceService: AuthResourceService, + private val client: Client, + private val config: CommonConfig +) : PermissionManageFacadeService { + override fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): SQLPage { + // 根据查询条件查询得到iam组id + val iamGroupIdsByConditions = listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = projectId, + groupName = groupName, + iamGroupIds = iamGroupIds, + relatedResourceType = relatedResourceType, + relatedResourceCode = relatedResourceCode, + action = action + ) + ) + // 查询成员所在资源用户组列表 + val (count, resourceGroupMembers) = listResourceGroupMembers( + projectCode = projectId, + memberId = memberId, + resourceType = resourceType, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt, + operateChannel = operateChannel, + start = start, + limit = limit + ) + // 用户组对应的资源信息 + val resourceGroupMap = authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectId, + iamGroupIds = resourceGroupMembers.map { it.iamGroupId.toString() } + ).associateBy { it.relationId } + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectId, + iamGroupIds = resourceGroupMembers.map { it.iamGroupId } + ) + // 用户组成员详情 + val groupMemberDetailMap = getGroupMemberDetailMap( + memberId = memberId, + resourceGroupMembers = resourceGroupMembers, + operateChannel = operateChannel + ) + // 获取用户正在交接的用户组,仅用于个人视角 + val groupsBeingHandover = if (operateChannel == OperateChannel.PERSONAL) { + permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = projectId, + memberId = memberId, + handoverType = HandoverType.GROUP + ) + } else { + emptyList() + } + val records = mutableListOf() + resourceGroupMembers.forEach { + val resourceGroup = resourceGroupMap[it.iamGroupId.toString()]!! + val groupMemberDetail = groupMemberDetailMap["${it.iamGroupId}_${it.memberId}"] + records.add( + convertGroupDetailsInfoVo( + resourceGroup = resourceGroup, + groupMemberDetail = groupMemberDetail, + uniqueManagerGroups = uniqueManagerGroups, + authResourceGroupMember = it, + operateChannel = operateChannel, + groupsBeingHandover = groupsBeingHandover + ) + ) + } + return SQLPage(count = count, records = records) + } + + private fun getGroupMemberDetailMap( + memberId: String, + resourceGroupMembers: List, + operateChannel: OperateChannel? + ): Map { + // 如果用户离职,查询权限中心接口会报错 + if (deptService.isUserDeparted(memberId)) { + return emptyMap() + } + // 用户组成员详情 + val groupMemberDetailMap = mutableMapOf() + // 直接加入的用户 + val userGroupIds = resourceGroupMembers + .filter { it.memberType == MemberType.USER.type } + .map { it.iamGroupId } + if (userGroupIds.isNotEmpty()) { + iamV2ManagerService.listMemberGroupsDetails( + MemberType.USER.type, + memberId, + userGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$memberId"] = it + } + } + val deptGroups = resourceGroupMembers + .filter { it.memberType == MemberType.DEPARTMENT.type } + when { + deptGroups.isEmpty() -> {} + operateChannel == OperateChannel.PERSONAL -> { + // 个人视角,会获取用户通过组织间接加入的组 + deptGroups.groupBy({ it.memberId }, { it.iamGroupId.toString() }) + .forEach { (deptId, iamGroupIds) -> + if (iamGroupIds.isEmpty()) return@forEach + iamV2ManagerService.listMemberGroupsDetails( + MemberType.DEPARTMENT.type, + deptId, + iamGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$deptId"] = it + } + } + } + + else -> { + // 管理员视角,获取组织直接加入的用户组 + val deptGroupIds = deptGroups.map { it.iamGroupId } + iamV2ManagerService.listMemberGroupsDetails( + MemberType.DEPARTMENT.type, + memberId, + deptGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$memberId"] = it + } + } + } + // 人员模板加入的组 + resourceGroupMembers.filter { it.memberType == MemberType.TEMPLATE.type } + .groupBy({ it.memberId }, { it.iamGroupId.toString() }) + .forEach { (iamTemplateId, iamGroupIds) -> + if (iamGroupIds.isEmpty()) return@forEach + iamV2ManagerService.listMemberGroupsDetails( + MemberType.TEMPLATE.type, + iamTemplateId, + iamGroupIds.joinToString(",") + ).forEach { + groupMemberDetailMap["${it.id}_$iamTemplateId"] = it + } + } + return groupMemberDetailMap + } + + private fun convertGroupDetailsInfoVo( + resourceGroup: TAuthResourceGroupRecord, + groupMemberDetail: MemberGroupDetailsResponse?, + uniqueManagerGroups: List, + authResourceGroupMember: AuthResourceGroupMember, + operateChannel: OperateChannel?, + groupsBeingHandover: List + ): GroupDetailsInfoVo { + // 如果用户离职,查询权限中心接口会报错,因此从数据库直接取数据,而不去调用权限中心接口。 + val (expiredAt, joinedTime) = if (groupMemberDetail != null) { + Pair( + TimeUnit.SECONDS.toMillis(groupMemberDetail.expiredAt), + TimeUnit.SECONDS.toMillis(groupMemberDetail.createdAt) + ) + } else { + Pair( + authResourceGroupMember.expiredTime.timestampmilli(), + 0L + ) + } + val between = expiredAt - System.currentTimeMillis() + val groupId = resourceGroup.relationId.toInt() + return GroupDetailsInfoVo( + resourceCode = resourceGroup.resourceCode, + resourceName = resourceGroup.resourceName, + resourceType = resourceGroup.resourceType, + groupId = groupId, + groupName = resourceGroup.groupName, + groupDesc = resourceGroup.description, + expiredAtDisplay = when { + expiredAt == PERMANENT_EXPIRED_TIME -> + I18nUtil.getCodeLanMessage(messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT) + + between >= 0 -> I18nUtil.getCodeLanMessage( + messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL, + params = arrayOf(DateTimeUtil.formatDay(between)) + ) + + else -> I18nUtil.getCodeLanMessage( + messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED + ) + }, + expiredAt = expiredAt, + joinedTime = joinedTime, + removeMemberButtonControl = when { + authResourceGroupMember.memberType == MemberType.TEMPLATE.type -> + RemoveMemberButtonControl.TEMPLATE + + operateChannel == OperateChannel.PERSONAL && + authResourceGroupMember.memberType == MemberType.DEPARTMENT.type -> + RemoveMemberButtonControl.DEPARTMENT + + resourceGroup.resourceType == AuthResourceType.PROJECT.value && + uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> + RemoveMemberButtonControl.UNIQUE_MANAGER + + uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> + RemoveMemberButtonControl.UNIQUE_OWNER + + else -> + RemoveMemberButtonControl.OTHER + }, + joinedType = when { + authResourceGroupMember.memberType == MemberType.TEMPLATE.type -> JoinedType.TEMPLATE + authResourceGroupMember.memberType == MemberType.DEPARTMENT.type && + operateChannel == OperateChannel.PERSONAL -> JoinedType.DEPARTMENT + + else -> JoinedType.DIRECT + }, + operator = "", + beingHandedOver = groupsBeingHandover.map { it.itemId.toInt() }.contains(groupId), + flowNo = groupsBeingHandover.firstOrNull { it.itemId.toInt() == groupId }?.flowNo, + memberType = MemberType.get(authResourceGroupMember.memberType) + ) + } + + override fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? + ): List { + val (iamTemplateIds, memberDeptInfos) = getMemberTemplateIdsAndDeptInfos( + projectCode = projectCode, + memberId = memberId, + operateChannel = operateChannel + ) + val iamGroupIdsByConditions = listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = projectCode, + groupName = groupName, + relatedResourceType = relatedResourceType, + relatedResourceCode = relatedResourceCode, + action = action + ) + ) + // 获取成员加入的用户组 + val memberGroupCountMap = authResourceGroupMemberDao.countMemberGroupOfResourceType( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, + maxExpiredAt = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, + memberDeptInfos = memberDeptInfos + ) + return rbacCommonService.convertResourceType2Count(memberGroupCountMap) + } + + private fun getMemberTemplateIdsAndDeptInfos( + projectCode: String, + memberId: String, + operateChannel: OperateChannel? + ): Pair, List> { + // 获取用户加入的项目级用户组模板ID + val iamTemplateIds = listProjectMemberGroupTemplateIds( + projectCode = projectCode, + memberId = memberId + ) + // 获取用户部门信息 + val memberDeptInfos = if (operateChannel == OperateChannel.PERSONAL) { + getMemberDeptInfos(memberId) + } else { + emptyList() + } + return Pair(iamTemplateIds, memberDeptInfos) + } + + override fun listIamGroupIdsByConditions(condition: IamGroupIdsQueryConditionDTO): List { + return with(condition) { + val filterGroupsByGroupName = if (isQueryByGroupName()) { + permissionResourceGroupService.listIamGroupIdsByGroupName( + projectId = projectCode, + groupName = groupName!! + ) + } else { + emptyList() + } + val finalGroupIds = if (isQueryByGroupPermissions()) { + groupPermissionService.listGroupsByPermissionConditions( + projectCode = projectCode, + filterIamGroupIds = filterGroupsByGroupName, + relatedResourceType = relatedResourceType!!, + relatedResourceCode = relatedResourceCode, + action = action + ) + } else { + filterGroupsByGroupName + }.toMutableList() + iamGroupIds?.let { finalGroupIds.addAll(it) } + finalGroupIds + } + } + + override fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List { + // 获取用户加入的项目级用户组模板ID + val iamTemplateIds = listProjectMemberGroupTemplateIds( + projectCode = projectCode, + memberId = memberId + ) + return authResourceGroupMemberDao.listMemberGroupIdsInProject( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds + ) + } + + @Suppress("LongParameterList") + override fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + operateChannel: OperateChannel?, + filterMemberType: MemberType?, + excludeIamGroupIds: List?, + onlyExcludeUserDirectlyJoined: Boolean?, + start: Int?, + limit: Int? + ): Pair> { + // 获取用户的部门信息以及加入的项目级别用户组模板ID + val (iamTemplateIds, memberDeptInfos) = getMemberTemplateIdsAndDeptInfos( + projectCode = projectCode, + memberId = memberId, + operateChannel = operateChannel + ) + + val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val count = authResourceGroupMemberDao.countMemberGroup( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + resourceType = resourceType, + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime, + memberDeptInfos = memberDeptInfos, + filterMemberType = filterMemberType, + excludeIamGroupIds = excludeIamGroupIds, + onlyExcludeUserDirectlyJoined = onlyExcludeUserDirectlyJoined + ) + val resourceGroupMembers = authResourceGroupMemberDao.listMemberGroupDetail( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = iamTemplateIds, + resourceType = resourceType, + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime, + memberDeptInfos = memberDeptInfos, + filterMemberType = filterMemberType, + excludeIamGroupIds = excludeIamGroupIds, + onlyExcludeUserDirectlyJoined = onlyExcludeUserDirectlyJoined, + offset = start, + limit = limit + ) + return Pair(count, resourceGroupMembers) + } + + // 获取用户加入的项目级用户组模板ID + private fun listProjectMemberGroupTemplateIds( + projectCode: String, + memberId: String + ): List { + // 查询项目下包含该成员的组列表 + val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( + dslContext = dslContext, + projectCode = projectCode, + resourceType = AuthResourceType.PROJECT.value, + memberId = memberId + ).map { it.iamGroupId.toString() } + // 通过项目组ID获取人员模板ID + return authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = projectGroupIds + ).filter { it.iamTemplateId != null } + .map { it.iamTemplateId.toString() } + } + + private fun getMemberDeptInfos( + memberId: String + ): List { + deptService.getUserInfo( + userId = "admin", + name = memberId + )?.deptInfo ?: return emptyList() + return deptService.getUserDeptInfo(memberId).toList() + } + + private fun getGroupIdsByGroupMemberCondition( + projectCode: String, + commonCondition: GroupMemberCommonConditionReq, + minExpiredAt: Long? = null + ): Map> { + val finalMemberGroups = mutableListOf() + + val resourceGroupMembersByCondition = when { + commonCondition.allSelection -> { + listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + operateChannel = commonCondition.operateChannel, + minExpiredAt = minExpiredAt + ).second + } + + commonCondition.resourceTypes.isNotEmpty() -> { + commonCondition.resourceTypes.flatMap { resourceType -> + listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + resourceType = resourceType, + operateChannel = commonCondition.operateChannel, + minExpiredAt = minExpiredAt + ).second + } + } + + else -> emptyList() + } + + finalMemberGroups.addAll(resourceGroupMembersByCondition) + if (commonCondition.groupIds.isNotEmpty()) { + val memberType2groupIds = commonCondition.groupIds.groupBy { it.memberType } + memberType2groupIds.forEach { (memberType, groupIds) -> + val groupsOfSelect = listResourceGroupMembers( + projectCode = projectCode, + memberId = commonCondition.targetMember.id, + iamGroupIds = groupIds.map { it.id }, + operateChannel = commonCondition.operateChannel, + filterMemberType = memberType, + minExpiredAt = minExpiredAt + ).second + finalMemberGroups.addAll(groupsOfSelect) + } + } + // 分类 + val result = mutableMapOf>() + finalMemberGroups.groupBy { it.memberType }.forEach { (memberType, groups) -> + result[MemberType.get(memberType)] = groups.map { it.iamGroupId } + } + return result + } + + override fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage { + logger.info("list project members by complex conditions: $conditionReq") + // 不允许同时查询部门名称和用户名称 + if (conditionReq.userName != null && conditionReq.deptName != null) { + return SQLPage(count = 0, records = emptyList()) + } + + // 简单查询直接返回结果 + if (!conditionReq.isComplexQuery()) { + return permissionResourceMemberService.listProjectMembers( + projectCode = conditionReq.projectCode, + memberType = conditionReq.memberType, + userName = conditionReq.userName, + deptName = conditionReq.deptName, + departedFlag = conditionReq.departedFlag, + page = conditionReq.page, + pageSize = conditionReq.pageSize + ) + } + + // 处理复杂查询条件 + val iamGroupIdsByCondition = if (conditionReq.isNeedToQueryIamGroups()) { + listIamGroupIdsByConditions( + condition = IamGroupIdsQueryConditionDTO( + projectCode = conditionReq.projectCode, + groupName = conditionReq.groupName, + relatedResourceType = conditionReq.relatedResourceType, + relatedResourceCode = conditionReq.relatedResourceCode, + action = conditionReq.action + ) + ) + } else { + emptyList() + }.toMutableList() + + // 查询不到用户组,直接返回空 + if (conditionReq.isNeedToQueryIamGroups() && iamGroupIdsByCondition.isEmpty()) { + return SQLPage(0, emptyList()) + } + + val conditionDTO = ProjectMembersQueryConditionDTO.build(conditionReq, iamGroupIdsByCondition) + + if (iamGroupIdsByCondition.isNotEmpty()) { + logger.debug("iamGroupIdsByCondition :{}", iamGroupIdsByCondition) + // 根据用户组Id查询出对应用户组中的人员模板成员 + val iamTemplateIds = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = ProjectMembersQueryConditionDTO( + projectCode = conditionDTO.projectCode, + queryTemplate = true, + iamGroupIds = conditionDTO.iamGroupIds + ) + ) + if (iamTemplateIds.isNotEmpty()) { + // 根据查询出的人员模板ID,查询出对应的组ID + val iamGroupIdsFromTemplate = authResourceGroupDao.listIamGroupIdsByConditions( + dslContext = dslContext, + projectCode = conditionDTO.projectCode, + iamTemplateIds = iamTemplateIds.map { it.id.toInt() } + ) + iamGroupIdsByCondition.addAll(iamGroupIdsFromTemplate) + logger.debug("iamGroupIdsByCondition and template :{}", iamGroupIdsByCondition) + } + } + + val records = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + logger.debug("listProjectMembersByComplexConditions :{}", records) + + val count = authResourceGroupMemberDao.countProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + logger.debug("listProjectMembersByComplexConditions :$count") + // 添加离职标志 + return if (conditionDTO.departedFlag == false) { + SQLPage(count, records) + } else { + SQLPage(count, permissionResourceMemberService.addDepartedFlagToMembers(records)) + } + } + + override fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIdsOfDirectlyJoined: List, + memberId: String + ): InvalidAuthorizationsDTO { + val startEpoch = System.currentTimeMillis() + try { + if (iamGroupIdsOfDirectlyJoined.isEmpty()) { + return InvalidAuthorizationsDTO( + invalidGroupIds = emptyList(), + invalidPipelineIds = emptyList(), + invalidRepertoryIds = emptyList(), + invalidEnvNodeIds = emptyList() + ) + } + + // 筛选出本次操作中未过期的用户组 + val iamGroupIdsOfNotExpired = getNotExpiredIamGroupIds( + projectCode = projectCode, + memberId = memberId, + iamGroupIds = iamGroupIdsOfDirectlyJoined + ) + // 获取用户退出/交接以上用户组后,还未退出的用户组(包含组织/直接/模板加入的组) + val (count, userGroupsJoinedAfterOperatedGroups) = listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + excludeIamGroupIds = iamGroupIdsOfDirectlyJoined, + onlyExcludeUserDirectlyJoined = true, + operateChannel = OperateChannel.PERSONAL, + minExpiredAt = LocalDateTime.now().timestampmilli() + ) + logger.debug( + "list all user groups joined after operated groups: {}, {}", + count, userGroupsJoinedAfterOperatedGroups + ) + + val isHasProjectVisitPermAfterOperatedGroups = checkProjectVisitPermission( + projectCode = projectCode, + iamGroupIds = userGroupsJoinedAfterOperatedGroups.map { it.iamGroupId } + ) + logger.debug( + "whether the user has project visit perm after operated groups: {}", + isHasProjectVisitPermAfterOperatedGroups + ) + + val invalidAuthorizationsDTO = if (count == 0L || !isHasProjectVisitPermAfterOperatedGroups) { + // 若用户已退出了所有的用户组或失去了项目访问权限,则直接返回项目下所有的授权 + getInvalidAuthorizationsAfterAllGroupsRemoved( + projectCode = projectCode, + memberId = memberId, + iamGroupIdsOfNotExpired = iamGroupIdsOfNotExpired + ) + } else { + val (invalidGroups, invalidPipelines) = getInvalidPipelinesAfterOperatedGroups( + projectCode = projectCode, + iamGroupIds = iamGroupIdsOfDirectlyJoined, + memberId = memberId, + iamGroupIdsOfNotExpired = iamGroupIdsOfNotExpired + ) + InvalidAuthorizationsDTO( + invalidGroupIds = invalidGroups, + invalidPipelineIds = invalidPipelines + ) + } + logger.info( + "invalid authorizations after operated groups|$projectCode|$iamGroupIdsOfDirectlyJoined|$memberId|" + + "$invalidAuthorizationsDTO" + ) + return invalidAuthorizationsDTO + } finally { + logger.info( + "It take(${System.currentTimeMillis() - startEpoch})ms to check invalid authorizations " + + "after operated groups |$projectCode|$iamGroupIdsOfDirectlyJoined|$memberId" + ) + } + } + + private fun getNotExpiredIamGroupIds( + projectCode: String, + memberId: String, + iamGroupIds: List + ): List { + return authResourceGroupMemberDao.listMemberGroupDetail( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamGroupIds = iamGroupIds, + minExpiredAt = LocalDateTime.now() + ).map { it.iamGroupId } + } + + private fun checkProjectVisitPermission( + projectCode: String, + iamGroupIds: List + ): Boolean { + return groupPermissionService.isGroupsHasPermission( + projectCode = projectCode, + filterIamGroupIds = iamGroupIds, + relatedResourceType = ResourceTypeId.PROJECT, + relatedResourceCode = projectCode, + action = ActionId.PROJECT_VISIT + ) + } + + private fun getInvalidAuthorizationsAfterAllGroupsRemoved( + projectCode: String, + memberId: String, + iamGroupIdsOfNotExpired: List + ): InvalidAuthorizationsDTO { + val invalidAuthorizations = authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = memberId + ) + ).groupBy({ it.resourceType }, { it.resourceCode }) + + val operatedGroupsWithExecutePerm = groupPermissionService.listGroupsByPermissionConditions( + projectCode = projectCode, + relatedResourceType = AuthResourceType.PIPELINE_DEFAULT.value, + action = ActionId.PIPELINE_EXECUTE, + filterIamGroupIds = iamGroupIdsOfNotExpired + ) + val invalidGroupIds = if (invalidAuthorizations.isNotEmpty()) { + operatedGroupsWithExecutePerm + } else { + emptyList() + } + return InvalidAuthorizationsDTO( + invalidGroupIds = invalidGroupIds, + invalidPipelineIds = invalidAuthorizations[ResourceTypeId.PIPELINE] ?: emptyList(), + invalidRepertoryIds = invalidAuthorizations[ResourceTypeId.REPERTORY] ?: emptyList(), + invalidEnvNodeIds = invalidAuthorizations[ResourceTypeId.ENV_NODE] ?: emptyList() + ) + } + + private fun getInvalidPipelinesAfterOperatedGroups( + projectCode: String, + iamGroupIds: List, + memberId: String, + iamGroupIdsOfNotExpired: List + ): InvalidAuthorizationsDTO { + logger.info("list invalid authorizations after operated groups:$projectCode|$iamGroupIds|$memberId") + val now = LocalDateTime.now() + logger.debug("list iam group ids of not expired:{}", iamGroupIdsOfNotExpired) + // 1.筛选出本次退出/交接中包含流水线执行权限的用户组 + val operatedGroupsWithExecutePerm = groupPermissionService.listGroupsByPermissionConditions( + projectCode = projectCode, + relatedResourceType = AuthResourceType.PIPELINE_DEFAULT.value, + action = ActionId.PIPELINE_EXECUTE, + filterIamGroupIds = iamGroupIdsOfNotExpired + ) + logger.debug("list operated groups with execute perm:{}", operatedGroupsWithExecutePerm) + if (operatedGroupsWithExecutePerm.isEmpty()) { + return InvalidAuthorizationsDTO(emptyList(), emptyList()) + } + + // 2.获取用户退出/交接以上操作的用户组后,还未退出并且未过期的流水线/项目级别(仅这些类型会包含流水线执行权限)的用户组。 + val userGroupsJoinedAfterOperatedGroups = listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + resourceType = ResourceTypeId.PIPELINE, + excludeIamGroupIds = iamGroupIdsOfNotExpired, + operateChannel = OperateChannel.PERSONAL, + onlyExcludeUserDirectlyJoined = true, + minExpiredAt = now.timestampmilli() + ).second.toMutableList().apply { + addAll( + listResourceGroupMembers( + projectCode = projectCode, + memberId = memberId, + resourceType = ResourceTypeId.PROJECT, + excludeIamGroupIds = iamGroupIdsOfNotExpired, + operateChannel = OperateChannel.PERSONAL, + onlyExcludeUserDirectlyJoined = true, + minExpiredAt = now.timestampmilli() + ).second + ) + }.map { it.iamGroupId } + logger.debug( + "list pipeline and project groups joined after operated groups:{}", + userGroupsJoinedAfterOperatedGroups + ) + + // 3.查询未退出的流水线/项目级别的用户组中是否包含项目级别的流水线执行权限。 + val hasAllPipelineExecutePermAfterOperateGroups = groupPermissionService.isGroupsHasProjectLevelPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + action = ActionId.PIPELINE_EXECUTE + ) + logger.debug( + "has all pipeline execute perm after operate groups:{}", + hasAllPipelineExecutePermAfterOperateGroups + ) + + // 3.1.若用户在未退出的组中拥有整个项目的流水线执行权限,则本次不会对任何的流水线代持人权限造成影响。 + if (hasAllPipelineExecutePermAfterOperateGroups) + return InvalidAuthorizationsDTO(emptyList(), emptyList()) + + // 3.2.若不包含整个项目的流水线执行权限,需查询本次退出/交接的用户组中是否包含项目级别的流水线执行权限。 + val hasAllPipelineExecutePermInOperateGroups = groupPermissionService.isGroupsHasProjectLevelPermission( + projectCode = projectCode, + filterIamGroupIds = operatedGroupsWithExecutePerm, + action = ActionId.PIPELINE_EXECUTE + ) + logger.debug("has all pipeline execute perm in operate groups:{}", hasAllPipelineExecutePermInOperateGroups) + + val pipelinesWithoutAuthorization = if (hasAllPipelineExecutePermInOperateGroups) { + // 3.2.1 如果本次退出/交接的用户组中包含项目级别的流水线执行权限, + // 那么查询出用户还有执行流水线权限的流水线,该项目下除了这些流水线,其他的流水线代持人权限都会失效。 + val userHasExecutePermAfterOperatedGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug("user has execute perm after operated groups:{}", userHasExecutePermAfterOperatedGroups) + // 失去代持人权限的流水线 + authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + handoverFrom = memberId, + excludeResourceCodes = userHasExecutePermAfterOperatedGroups + ) + ).map { it.resourceCode } + } else { + // 3.2.2 如果本次退出/交接的用户组中不包含整个项目的流水线执行权限。 + // 通过计算得出,用户本次操作用户组,导致失去流水线执行权限的流水线。 + // 然后再计算失去这些流水线执行权限后,会导致哪些流水线的代持人权限失效。 + val pipelinesWithExecutePermAfterOperatedGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = userGroupsJoinedAfterOperatedGroups, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug( + "pipelines with execute perm after operate groups:{}", + pipelinesWithExecutePermAfterOperatedGroups + ) + + val pipelinesWithExecutePermInOperateGroups = groupPermissionService.listGroupResourcesWithPermission( + projectCode = projectCode, + filterIamGroupIds = operatedGroupsWithExecutePerm, + relatedResourceType = ResourceTypeId.PIPELINE, + action = ActionId.PIPELINE_EXECUTE + )[ResourceTypeId.PIPELINE] ?: emptyList() + logger.debug("pipelines with execute perm in operate groups:{}", pipelinesWithExecutePermInOperateGroups) + + val pipelineExecutePermLostFromUser = pipelinesWithExecutePermInOperateGroups.filterNot { + pipelinesWithExecutePermAfterOperatedGroups.contains(it) + } + // 失去代持人权限的流水线 + authAuthorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + handoverFrom = memberId, + filterResourceCodes = pipelineExecutePermLostFromUser + ) + ).map { it.resourceCode } + } + logger.debug("pipelines without authorization:{}", pipelinesWithoutAuthorization) + if (pipelinesWithoutAuthorization.isNotEmpty()) { + return InvalidAuthorizationsDTO( + invalidGroupIds = operatedGroupsWithExecutePerm, + invalidPipelineIds = pipelinesWithoutAuthorization + ) + } + + return InvalidAuthorizationsDTO(emptyList(), emptyList()) + } + + override fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean { + logger.info("renewal group member $userId|$projectCode|$renewalConditionReq") + val groupId = renewalConditionReq.groupId + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.RENEWAL, + conditionReq = GroupMemberRenewalConditionReq( + groupIds = listOf( + MemberGroupJoinedDTO( + id = groupId, + memberType = MemberType.get(renewalConditionReq.targetMember.type) + ) + ), + targetMember = renewalConditionReq.targetMember, + renewalDuration = renewalConditionReq.renewalDuration + ), + operateGroupMemberTask = ::renewalTask + ) + return true + } + + private fun renewalTask( + projectCode: String, + groupId: Int, + renewalConditionReq: GroupMemberRenewalConditionReq, + expiredAt: Long + ) { + logger.info("renewal group member ${renewalConditionReq.targetMember}|$projectCode|$groupId|$expiredAt") + val targetMember = renewalConditionReq.targetMember + if (targetMember.type == MemberType.USER.type && deptService.isUserDeparted(targetMember.id)) { + return + } + val secondsOfRenewalDuration = TimeUnit.DAYS.toSeconds(renewalConditionReq.renewalDuration.toLong()) + val secondsOfCurrentTime = System.currentTimeMillis() / 1000 + // 若权限已过期,则为当前时间+续期天数,若未过期,则为有效期+续期天数 + val finalExpiredAt = if (expiredAt < secondsOfCurrentTime) { + secondsOfCurrentTime + } else { + expiredAt + } + secondsOfRenewalDuration + if (!isNeedToRenewal(finalExpiredAt)) { + return + } + permissionResourceMemberService.renewalIamGroupMembers( + groupId = groupId, + members = listOf(ManagerMember(targetMember.type, targetMember.id)), + expiredAt = finalExpiredAt + ) + authResourceGroupMemberDao.update( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt), + memberId = targetMember.id + ) + } + + private fun isNeedToRenewal(expiredAt: Long): Boolean { + return expiredAt < PERMANENT_EXPIRED_TIME / 1000 + } + + override fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean { + logger.info("batch renewal group member $userId|$projectCode|$renewalConditionReq") + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.RENEWAL, + conditionReq = renewalConditionReq, + operateGroupMemberTask = ::renewalTask + ) + return true + } + + override fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean { + logger.info("batch handover group members from manager $userId|$projectCode|$handoverMemberDTO") + handoverMemberDTO.checkHandoverTo() + // 若交接对象是部门,直接进行交接 + if (handoverMemberDTO.targetMember.type == MemberType.DEPARTMENT.type) { + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + } + // 若操作对象是用户,需要将被影响流水线授权一并交接给授权人 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = handoverMemberDTO + )[MemberType.USER] ?: return true + // 获取导致失效的流水线/代码库授权/环境节点授权,并进行交接 + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIds, + memberId = handoverMemberDTO.targetMember.id + ) + // 检查授予人是否有代码库oauth权限 + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id + ) + } + // 交接用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + handoverAuthorizationsWhenOperatedGroups( + userId = userId, + projectCode = projectCode, + invalidPipelines = invalidPipelines, + invalidRepertoryIds = invalidRepertoryIds, + invalidEnvNodeIds = invalidEnvNodeIds, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id + ) + return true + } + + private fun handoverAuthorizationsWhenOperatedGroups( + userId: String, + projectCode: String, + invalidRepertoryIds: List, + invalidPipelines: List, + invalidEnvNodeIds: List, + handoverFrom: String, + handoverTo: String + ) { + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + fullSelection = true, + filterResourceCodes = invalidRepertoryIds, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverFrom, + handoverTo = handoverTo, + checkPermission = false + ) + ) + } + if (invalidPipelines.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.PIPELINE, + fullSelection = true, + filterResourceCodes = invalidPipelines, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverFrom, + handoverTo = handoverTo, + checkPermission = false + ) + ) + } + if (invalidEnvNodeIds.isNotEmpty()) { + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = userId, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.ENV_NODE, + fullSelection = true, + filterResourceCodes = invalidEnvNodeIds, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverFrom, + handoverTo = handoverTo, + checkPermission = false + ) + ) + } + } + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): String { + logger.info("batch handover group members from personal $userId|$projectCode|$handoverMemberDTO") + handoverMemberDTO.checkHandoverTo() + // 成员直接加入的组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = handoverMemberDTO, + minExpiredAt = LocalDateTime.now().timestampmilli() + )[MemberType.get(MemberType.USER.type)]?.toMutableList() + if (groupIds.isNullOrEmpty()) { + throw ErrorCodeException( + errorCode = AuthMessageCode.GROUP_NOT_EXIST + ) + } + + // 过滤掉审核中的用户组 + val beingHandoverGroups = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = projectCode, + memberId = handoverMemberDTO.targetMember.id, + handoverType = HandoverType.GROUP + ).map { it.itemId.toInt() } + groupIds.removeAll(beingHandoverGroups) + // 本次操作导致失效的授权 + val invalidAuthorizations = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIds, + memberId = handoverMemberDTO.targetMember.id + ) + + val invalidPipelines = invalidAuthorizations.invalidPipelineIds + val invalidRepertoryIds = invalidAuthorizations.invalidRepertoryIds + val invalidEnvNodeIds = invalidAuthorizations.invalidEnvNodeIds + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = handoverMemberDTO.targetMember.id, + handoverTo = handoverMemberDTO.handoverTo.id + ) + } + val handoverDetails = buildHandoverDetails( + projectCode = projectCode, + groupIds = groupIds.map { it.toString() }, + invalidPipelines = invalidPipelines, + invalidRepertoryIds = invalidRepertoryIds, + invalidEnvNodeIds = invalidEnvNodeIds + ) + val projectName = authResourceService.get( + projectCode = projectCode, + resourceType = ResourceTypeId.PROJECT, + resourceCode = projectCode + ).resourceName + // 创建交接单 + val flowNo = permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + projectName = projectName, + applicant = handoverMemberDTO.targetMember.id, + approver = handoverMemberDTO.handoverTo.id, + handoverStatus = HandoverStatus.PENDING, + groupCount = groupIds.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ), + details = handoverDetails + ) + return flowNo + } + + private fun buildHandoverDetails( + projectCode: String, + groupIds: List, + invalidPipelines: List, + invalidRepertoryIds: List, + invalidEnvNodeIds: List + ): List { + val handoverDetails = mutableListOf() + if (groupIds.isNotEmpty()) { + val resourceGroups = authResourceGroupDao.listByRelationId( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIds + ) + resourceGroups.forEach { groupInfo -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + itemId = groupInfo.relationId, + resourceType = groupInfo.resourceType, + handoverType = HandoverType.GROUP + ) + ) + } + } + + invalidPipelines.forEach { pipelineId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + itemId = pipelineId, + resourceType = ResourceTypeId.PIPELINE, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + invalidRepertoryIds.forEach { repertoryId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + itemId = repertoryId, + resourceType = ResourceTypeId.REPERTORY, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + invalidEnvNodeIds.forEach { envNodeId -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + itemId = envNodeId, + resourceType = ResourceTypeId.ENV_NODE, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + return handoverDetails + } + + override fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean { + logger.info("batch delete group members $userId|$projectCode|$removeMemberDTO") + // 若操作对象是组织,则直接退出即可。 + if (removeMemberDTO.targetMember.type == MemberType.DEPARTMENT.type) { + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = removeMemberDTO, + operateGroupMemberTask = ::deleteTask + ) + return true + } + // 以下逻辑是用户类型成员的批量移出组 + // 根据条件获取成员直接加入的用户组 + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = removeMemberDTO + )[MemberType.USER] ?: return true + + val invalidAuthorizationsDTO = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIdsDirectlyJoined, + memberId = removeMemberDTO.targetMember.id + ) + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = invalidAuthorizationsDTO + + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id + ) + } + // 获取唯一管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val (toHandoverGroups, toDeleteGroups) = groupIdsDirectlyJoined.partition { + uniqueManagerGroups.contains(it) || invalidGroups.contains(it) + } + // 直接退出的用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = GroupMemberRemoveConditionReq( + groupIds = toDeleteGroups.map { + MemberGroupJoinedDTO( + id = it, + memberType = MemberType.USER + ) + }, + targetMember = removeMemberDTO.targetMember + ), + operateGroupMemberTask = ::deleteTask + ) + // 交接唯一拥有者、影响代持人权限的用户组以及流水线/代码库授权/环境节点授权 + if (toHandoverGroups.isNotEmpty()) { + removeMemberDTO.checkHandoverTo() + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = GroupMemberHandoverConditionReq( + groupIds = toHandoverGroups.map { + MemberGroupJoinedDTO( + id = it, + memberType = MemberType.USER + ) + }, + targetMember = removeMemberDTO.targetMember, + handoverTo = removeMemberDTO.handoverTo!! + ), + operateGroupMemberTask = ::handoverTask + ) + } + if (invalidAuthorizationsDTO.isHasInvalidAuthorizations()) { + handoverAuthorizationsWhenOperatedGroups( + userId = userId, + projectCode = projectCode, + invalidPipelines = invalidPipelines, + invalidRepertoryIds = invalidRepertoryIds, + invalidEnvNodeIds = invalidEnvNodeIds, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id + ) + } + return true + } + + override fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): String { + logger.info("batch delete group members from personal $userId|$projectCode|$removeMemberDTO") + // 根据条件获取成员直接加入的用户组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = removeMemberDTO + )[MemberType.USER]?.toMutableList() ?: return "true" + + // 过滤掉审核中的用户组 + val beingHandoverGroups = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = projectCode, + memberId = removeMemberDTO.targetMember.id, + handoverType = HandoverType.GROUP + ).map { it.itemId.toInt() } + groupIds.removeAll(beingHandoverGroups) + + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIds, + memberId = removeMemberDTO.targetMember.id + ) + + // 检查授予人是否有代码库oauth权限 + if (invalidRepertoryIds.isNotEmpty()) { + permissionAuthorizationService.checkRepertoryAuthorizationsHanover( + operator = userId, + projectCode = projectCode, + repertoryIds = invalidRepertoryIds, + handoverFrom = removeMemberDTO.targetMember.id, + handoverTo = removeMemberDTO.handoverTo!!.id + ) + } + + // 获取唯一管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIds + ) + val (toHandoverGroups, toDeleteGroups) = groupIds.partition { + uniqueManagerGroups.contains(it) || invalidGroups.contains(it) + } + // 直接退出的用户组 + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = GroupMemberRemoveConditionReq( + groupIds = toDeleteGroups.map { + MemberGroupJoinedDTO( + id = it, + memberType = MemberType.USER + ) + }, + targetMember = removeMemberDTO.targetMember + ), + operateGroupMemberTask = ::deleteTask + ) + if (toHandoverGroups.isEmpty() && invalidPipelines.isEmpty() && invalidRepertoryIds.isEmpty() && + invalidEnvNodeIds.isEmpty()) { + return "true" + } + val handoverDetails = buildHandoverDetails( + projectCode = projectCode, + groupIds = toHandoverGroups.map { it.toString() }, + invalidPipelines = invalidPipelines, + invalidRepertoryIds = invalidRepertoryIds, + invalidEnvNodeIds = invalidEnvNodeIds + ) + + val projectName = authResourceService.get( + projectCode = projectCode, + resourceType = ResourceTypeId.PROJECT, + resourceCode = projectCode + ).resourceName + val flowNo = permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + projectName = projectName, + applicant = removeMemberDTO.targetMember.id, + approver = removeMemberDTO.handoverTo!!.id, + handoverStatus = HandoverStatus.PENDING, + groupCount = toHandoverGroups.size, + authorizationCount = invalidPipelines.size + invalidRepertoryIds.size + ), + details = handoverDetails + ) + return flowNo + } + + override fun deleteResourceGroupMembers( + userId: String, + projectCode: String, + groupId: Int, + targetMember: ResourceMemberInfo + ): Boolean { + logger.info("delete single group members from personal:$userId|$targetMember|$projectCode|$groupId") + if (targetMember.type == MemberType.USER.type) { + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = listOf(groupId), + memberId = targetMember.id + ) + if (invalidGroups.isNotEmpty() || invalidPipelines.isNotEmpty() || + invalidRepertoryIds.isNotEmpty() || invalidEnvNodeIds.isNotEmpty()) { + throw ErrorCodeException(errorCode = ERROR_SINGLE_GROUP_REMOVE) + } + } + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = GroupMemberRemoveConditionReq( + groupIds = listOf( + MemberGroupJoinedDTO( + id = groupId, + memberType = MemberType.get(targetMember.type) + ) + ), + targetMember = targetMember + ), + operateGroupMemberTask = ::deleteTask + ) + return true + } + + private fun handoverTask( + projectCode: String, + groupId: Int, + handoverMemberDTO: GroupMemberHandoverConditionReq, + expiredAt: Long + ) { + logger.info( + "handover group member $projectCode|$groupId|" + + "${handoverMemberDTO.targetMember}|${handoverMemberDTO.handoverTo}" + ) + val currentTimeSeconds = System.currentTimeMillis() / 1000 + var finalExpiredAt = expiredAt + // 若交接人的权限已过期,如果是唯一管理员组,允许交接,接收人将获得半年权限;其他的直接删除。 + if (expiredAt < currentTimeSeconds) { + val isUniqueManagerGroup = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = listOf(groupId) + ).isNotEmpty() + if (isUniqueManagerGroup) { + finalExpiredAt = currentTimeSeconds + TimeUnit.DAYS.toSeconds(180) + } else { + deleteTask( + projectCode = projectCode, + groupId = groupId, + removeMemberDTO = GroupMemberRemoveConditionReq( + targetMember = handoverMemberDTO.targetMember + ), + expiredAt = finalExpiredAt + ) + return + } + } + + val isHandoverToInGroup = authResourceGroupMemberDao.isMemberInGroup( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + memberId = handoverMemberDTO.handoverTo.id + ) + if (isHandoverToInGroup) { + deleteTask( + projectCode = projectCode, + groupId = groupId, + removeMemberDTO = GroupMemberRemoveConditionReq( + targetMember = handoverMemberDTO.handoverTo + ), + expiredAt = finalExpiredAt + ) + } + if (finalExpiredAt < currentTimeSeconds) { + throw ErrorCodeException( + errorCode = AuthMessageCode.INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER + ) + } + val members = listOf( + ManagerMember( + handoverMemberDTO.handoverTo.type, + handoverMemberDTO.handoverTo.id + ) + ) + permissionResourceMemberService.addIamGroupMember( + groupId = groupId, + members = members, + expiredAt = finalExpiredAt + ) + permissionResourceMemberService.deleteIamGroupMembers( + groupId = groupId, + type = handoverMemberDTO.targetMember.type, + memberIds = listOf(handoverMemberDTO.targetMember.id) + ) + authResourceGroupMemberDao.handoverGroupMembers( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + handoverFrom = handoverMemberDTO.targetMember, + handoverTo = handoverMemberDTO.handoverTo, + expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt) + ) + } + + private fun deleteTask( + projectCode: String, + groupId: Int, + removeMemberDTO: GroupMemberRemoveConditionReq, + expiredAt: Long + ) { + val targetMember = removeMemberDTO.targetMember + logger.info("delete group member $projectCode|$groupId|$targetMember") + permissionResourceMemberService.deleteIamGroupMembers( + groupId = groupId, + type = targetMember.type, + memberIds = listOf(targetMember.id) + ) + authResourceGroupMemberDao.batchDeleteGroupMembers( + dslContext = dslContext, + projectCode = projectCode, + iamGroupId = groupId, + memberIds = listOf(removeMemberDTO.targetMember.id) + ) + } + + override fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo { + logger.info("batch operate group member check|$userId|$projectCode|$batchOperateType|$conditionReq") + // 获取成员加入的用户组 + val joinedType2GroupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = conditionReq + ) + // 通过组织或者模板加入的用户组 + val groupsOfTemplateOrDeptJoined = when (conditionReq.targetMember.type) { + MemberType.USER.type -> { + listOfNotNull( + joinedType2GroupIds[MemberType.DEPARTMENT], + joinedType2GroupIds[MemberType.TEMPLATE] + ).flatten() + } + + else -> joinedType2GroupIds[MemberType.TEMPLATE] ?: emptyList() + } + // 直接加入的组 + val groupsOfDirectlyJoined = joinedType2GroupIds[MemberType.get(conditionReq.targetMember.type)] ?: emptyList() + // 总数 + val totalCount = groupsOfTemplateOrDeptJoined.size + groupsOfDirectlyJoined.size + return when (batchOperateType) { + BatchOperateType.REMOVE -> { + if (conditionReq.targetMember.type == MemberType.DEPARTMENT.type) { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = groupsOfDirectlyJoined.size, + inoperableCount = groupsOfTemplateOrDeptJoined.size + ) + } else { + val groupsOfUniqueManager = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined + ) + // 本次操作导致流水线代持人权限受到影响的用户组及流水线/代码库oauth/环境节点 + val invalidAuthorizationsDTO = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupsOfDirectlyJoined, + memberId = conditionReq.targetMember.id + ) + val (invalidGroups, invalidPipelines, invalidRepositoryIds, invalidEnvNodeIds) = + invalidAuthorizationsDTO + + // 当批量移出时, + // 直接加入的组中,唯一管理员组/影响流水线代持权限不允许被移出 + // 间接加入的组中,通过组织、模板加入的组不允许被移出 + val groupsOfInOperableWhenBatchRemove = groupsOfDirectlyJoined.count { + groupsOfUniqueManager.contains(it) || invalidGroups.contains(it) + } + groupsOfTemplateOrDeptJoined.size + val canHandoverCount = groupsOfUniqueManager.union(invalidGroups).size + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - groupsOfInOperableWhenBatchRemove, + inoperableCount = groupsOfInOperableWhenBatchRemove, + uniqueManagerCount = groupsOfUniqueManager.size, + invalidGroupCount = invalidGroups.size, + invalidPipelineAuthorizationCount = invalidPipelines.size, + invalidRepositoryAuthorizationCount = invalidRepositoryIds.size, + invalidEnvNodeAuthorizationCount = invalidEnvNodeIds.size, + canHandoverCount = canHandoverCount, + needToHandover = invalidAuthorizationsDTO.isHasInvalidAuthorizations() || canHandoverCount > 0 + ) + } + } + + BatchOperateType.RENEWAL -> { + // 部门/组织加入以及永久权限的组不允许再续期 + with(conditionReq) { + val isUserDeparted = targetMember.type == MemberType.USER.type && + deptService.isUserDeparted(targetMember.id) + // 离职用户不允许续期 + if (isUserDeparted) { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + inoperableCount = totalCount + ) + } else { + // 永久期限 不允许再续期 + val groupCountOfPermanentExpiredTime = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = groupsOfDirectlyJoined + ).filter { + // iam用的是秒级时间戳 + it.expiredAt == PERMANENT_EXPIRED_TIME / 1000 + }.size + val groupsOfInOperableWhenBatchRenewal = groupCountOfPermanentExpiredTime + + groupsOfTemplateOrDeptJoined.size + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - groupsOfInOperableWhenBatchRenewal, + inoperableCount = groupsOfInOperableWhenBatchRenewal + ) + } + } + } + + BatchOperateType.HANDOVER -> { + // 已过期(除唯一管理员组 )或通过模板/组织加入的不允许移交 + with(conditionReq) { + val finalGroupIds = groupsOfDirectlyJoined.toMutableList() + val uniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupsOfDirectlyJoined + ) + // 去除唯一管理员组 + if (uniqueManagerGroupIds.isNotEmpty()) { + finalGroupIds.removeAll(uniqueManagerGroupIds) + } + val groupCountOfExpired = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = finalGroupIds + ).filter { + // iam用的是秒级时间戳 + it.expiredAt < System.currentTimeMillis() / 1000 + }.size + val inoperableCount = groupsOfTemplateOrDeptJoined.size + groupCountOfExpired + // 本次操作导致流水线代持人权限受到影响的流水线 + val (invalidGroups, invalidPipelines, invalidRepositoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupsOfDirectlyJoined, + memberId = conditionReq.targetMember.id + ) + + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + operableCount = totalCount - inoperableCount, + inoperableCount = groupsOfTemplateOrDeptJoined.size + groupCountOfExpired, + invalidPipelineAuthorizationCount = invalidPipelines.size, + invalidRepositoryAuthorizationCount = invalidRepositoryIds.size, + invalidEnvNodeAuthorizationCount = invalidEnvNodeIds.size, + canHandoverCount = totalCount - inoperableCount + ) + } + } + + else -> { + BatchOperateGroupMemberCheckVo( + totalCount = totalCount, + inoperableCount = groupsOfTemplateOrDeptJoined.size, + operableCount = groupsOfDirectlyJoined.size + ) + } + } + } + + override fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List { + logger.info("remove member from project $userId|$projectCode|$removeMemberFromProjectReq") + return with(removeMemberFromProjectReq) { + val memberType = targetMember.type + val isNeedToHandover = handoverTo != null + if (memberType == MemberType.USER.type && isNeedToHandover) { + removeMemberFromProjectReq.checkHandoverTo() + val handoverMemberDTO = GroupMemberHandoverConditionReq( + allSelection = true, + targetMember = targetMember, + handoverTo = handoverTo!! + ) + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = handoverMemberDTO, + operateGroupMemberTask = ::handoverTask + ) + permissionAuthorizationService.resetAllResourceAuthorization( + operator = userId, + projectCode = projectCode, + condition = ResetAllResourceAuthorizationReq( + projectCode = projectCode, + handoverFrom = removeMemberFromProjectReq.targetMember.id, + handoverTo = removeMemberFromProjectReq.handoverTo!!.id, + preCheck = false, + checkPermission = false + ) + ) + } else { + val removeMemberDTO = GroupMemberRemoveConditionReq( + allSelection = true, + targetMember = targetMember + ) + batchOperateGroupMembers( + projectCode = projectCode, + type = BatchOperateType.REMOVE, + conditionReq = removeMemberDTO, + operateGroupMemberTask = ::deleteTask + ) + } + + if (memberType == MemberType.USER.type) { + // 查询用户还存在那些组织中 + val userDeptInfos = deptService.getUserInfo( + userId = "admin", + name = targetMember.id + )?.deptInfo?.map { it.name!! } + if (userDeptInfos != null) { + return authResourceGroupMemberDao.isMembersInProject( + dslContext = dslContext, + projectCode = projectCode, + memberNames = userDeptInfos, + memberType = MemberType.DEPARTMENT.type + ) + } + } + return emptyList() + } + } + + override fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean { + val targetMember = removeMemberFromProjectReq.targetMember + val isMemberHasNoPermission = batchOperateGroupMembersCheck( + userId = userId, + projectCode = projectCode, + batchOperateType = BatchOperateType.HANDOVER, + conditionReq = GroupMemberCommonConditionReq( + allSelection = true, + targetMember = removeMemberFromProjectReq.targetMember + ) + ).let { it.totalCount == it.inoperableCount } + + val isMemberHasNoAuthorizations = + if (targetMember.type == MemberType.USER.type) { + permissionAuthorizationService.listResourceAuthorizations( + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = targetMember.id + ) + ).count == 0L + } else { + true + } + return isMemberHasNoPermission && isMemberHasNoAuthorizations + } + + override fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean { + val overview = permissionHandoverApplicationService.getHandoverOverview(request.flowNo) + logger.info("handle hanover application:{}|{} ", request, overview) + HandleHandoverApplicationLock(redisOperation, request.flowNo).use { lock -> + if (!lock.tryLock()) { + logger.warn("The handover application is being processed!$request") + throw ErrorCodeException(errorCode = ERROR_HANDOVER_HANDLE) + } + try { + handleHanoverCheck(request = request, overview = overview) + if (request.handoverAction == HandoverAction.AGREE) { + handleHandoverAgreeAction( + request = request, + overview = overview + ) + } + permissionHandoverApplicationService.updateHandoverApplication( + overview = request + ) + val projectName = authResourceService.get( + projectCode = request.projectCode, + resourceType = ResourceTypeId.PROJECT, + resourceCode = request.projectCode + ).resourceName + val handoverFromCnName = deptService.getMemberInfo( + overview.applicant, ManagerScopesEnum.USER + ).displayName + val handoverToCnName = deptService.getMemberInfo( + overview.approver, ManagerScopesEnum.USER + ).displayName + val bodyParams = mapOf( + "projectName" to projectName, + "result" to request.handoverAction.alias, + "handoverFrom" to overview.applicant.plus("($handoverFromCnName)"), + "remark" to request.remark!!, + "content" to String.format( + request.handoverAction.emailContent, + request.flowNo, + overview.approver.plus("($handoverToCnName)"), + ), + "weworkContent" to String.format( + request.handoverAction.weworkContent, + request.flowNo, + overview.approver.plus("($handoverToCnName)"), + ), + "url" to String.format(url, request.flowNo) + ) + // 发邮件 + val emailRequest = SendNotifyMessageTemplateRequest( + templateCode = HANDOVER_APPLICATION_RESULT_TEMPLATE_CODE, + bodyParams = bodyParams, + titleParams = bodyParams, + notifyType = mutableSetOf(NotifyType.WEWORK.name, NotifyType.EMAIL.name), + receivers = mutableSetOf(overview.applicant) + ) + logger.info("send handover application result email:{}|{} ", request, emailRequest) + kotlin.runCatching { + client.get(ServiceNotifyMessageTemplateResource::class).sendNotifyMessageByTemplate(emailRequest) + }.onFailure { + logger.warn("notify email fail ${it.message}|$bodyParams|${overview.approver}") + } + } catch (e: Exception) { + logger.warn("handle hanover application error,$e|$request") + throw e + } + } + return true + } + + override fun batchHandleHanoverApplications(request: HandoverOverviewBatchUpdateReq): Boolean { + logger.info("batch handle hanover application:{} ", request) + val startEpoch = System.currentTimeMillis() + try { + val overviews = when { + request.allSelection -> permissionHandoverApplicationService.listHandoverOverviews( + queryRequest = HandoverOverviewQueryReq( + memberId = request.operator, + approver = request.operator, + handoverStatus = HandoverStatus.PENDING + ) + ) + + request.flowNos.isNotEmpty() -> permissionHandoverApplicationService.listHandoverOverviews( + queryRequest = HandoverOverviewQueryReq( + memberId = request.operator, + approver = request.operator, + handoverStatus = HandoverStatus.PENDING, + flowNos = request.flowNos + ) + ) + + else -> return true + }.records + overviews.forEach { overview -> + handleHanoverApplication( + request = HandoverOverviewUpdateReq( + projectCode = overview.projectCode, + flowNo = overview.flowNo, + operator = request.operator, + handoverAction = request.handoverAction, + remark = request.remark + ) + ) + } + } finally { + "It take(${System.currentTimeMillis() - startEpoch})ms to batch handle hanover applications" + } + return true + } + + override fun getResourceType2CountOfHandover( + queryReq: ResourceType2CountOfHandoverQuery + ): List { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.getResourceType2CountOfHandoverApplication(queryReq.flowNo!!) + } else { + getResourceType2CountOfHandoverPreview(queryReq) + } + } + + // 交接预览 + private fun getResourceType2CountOfHandoverPreview( + queryReq: ResourceType2CountOfHandoverQuery + ): List { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val batchOperateType = queryReq.batchOperateType!! + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return emptyList() + + val result = mutableListOf() + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ) + if (batchOperateType == BatchOperateType.REMOVE) { + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val needToHandoverGroupIds = invalidGroups.union(uniqueManagerGroups).map { it.toString() } + val resourceType2CountOfGroup = authResourceGroupDao.getResourceType2Count( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = needToHandoverGroupIds + ) + if (resourceType2CountOfGroup.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = resourceType2CountOfGroup, + type = HandoverType.GROUP + ) + ) + } + } + if (invalidPipelines.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = mapOf(ResourceTypeId.PIPELINE to invalidPipelines.size.toLong()), + type = HandoverType.AUTHORIZATION + ) + ) + } + if (invalidRepertoryIds.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = mapOf(ResourceTypeId.REPERTORY to invalidRepertoryIds.size.toLong()), + type = HandoverType.AUTHORIZATION + ) + ) + } + if (invalidEnvNodeIds.isNotEmpty()) { + result.addAll( + rbacCommonService.convertResourceType2Count( + resourceType2Count = mapOf(ResourceTypeId.ENV_NODE to invalidEnvNodeIds.size.toLong()), + type = HandoverType.AUTHORIZATION + ) + ) + } + return result + } + + override fun listAuthorizationsOfHandover( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.listAuthorizationsOfHandoverApplication(queryReq) + } else { + listAuthorizationsOfHandoverPreview(queryReq) + } + } + + private fun listAuthorizationsOfHandoverPreview( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return SQLPage(0, emptyList()) + val (invalidGroups, invalidPipelines, invalidRepertoryIds, invalidEnvNodeIds) = + listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ) + + val invalidResources = when (queryReq.resourceType) { + ResourceTypeId.PIPELINE -> invalidPipelines + ResourceTypeId.ENV_NODE -> invalidEnvNodeIds + else -> invalidRepertoryIds + } + + val records = authorizationDao.list( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + resourceType = queryReq.resourceType, + filterResourceCodes = invalidResources, + page = queryReq.page, + pageSize = queryReq.pageSize + ) + ).map { + HandoverAuthorizationDetailVo( + resourceCode = it.resourceCode, + resourceName = it.resourceName, + handoverType = HandoverType.AUTHORIZATION, + handoverFrom = it.handoverFrom + ) + } + return SQLPage(count = invalidResources.size.toLong(), records = records) + } + + override fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + queryReq.check() + return if (queryReq.queryChannel == HandoverQueryChannel.HANDOVER_APPLICATION) { + permissionHandoverApplicationService.listGroupsOfHandoverApplication(queryReq) + } else { + listGroupsOfHandoverPreview(queryReq) + } + } + + override fun isProjectMember( + projectCode: String, + userId: String + ): Boolean { + // 获取用户加入的项目级用户组模板ID + val iamTemplateIds = listProjectMemberGroupTemplateIds( + projectCode = projectCode, + memberId = userId + ) + val memberDeptInfos = deptService.getUserInfo( + userId = "admin", + name = userId + )?.deptInfo?.map { it.name!! } + + return authResourceGroupMemberDao.isMemberInProject( + dslContext = dslContext, + projectCode = projectCode, + userId = userId, + iamTemplateIds = iamTemplateIds, + memberDeptInfos = memberDeptInfos + ) || rbacCommonService.validateUserProjectPermission( + userId = userId, + projectCode = projectCode, + permission = AuthPermission.VISIT + ) + } + + private fun listGroupsOfHandoverPreview(queryReq: HandoverDetailsQueryReq): SQLPage { + val projectCode = queryReq.projectCode + val previewConditionReq = queryReq.previewConditionReq!! + val convertPageSizeToSQLLimit = PageUtil.convertPageSizeToSQLLimit(queryReq.page, queryReq.pageSize) + val groupIdsDirectlyJoined = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = previewConditionReq + )[MemberType.USER] ?: return SQLPage(0, emptyList()) + val invalidGroupIds = listInvalidAuthorizationsAfterOperatedGroups( + projectCode = projectCode, + iamGroupIdsOfDirectlyJoined = groupIdsDirectlyJoined, + memberId = previewConditionReq.targetMember.id + ).invalidGroupIds + // 只有一个成员的管理员组 + val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( + dslContext = dslContext, + projectCode = projectCode, + iamGroupIds = groupIdsDirectlyJoined + ) + val needToHandoverGroupIds = invalidGroupIds.union(uniqueManagerGroups).map { it.toString() } + val records = authResourceGroupDao.listGroupByResourceType( + dslContext = dslContext, + projectCode = projectCode, + resourceType = queryReq.resourceType, + iamGroupIds = needToHandoverGroupIds, + offset = convertPageSizeToSQLLimit.offset, + limit = convertPageSizeToSQLLimit.limit + ).map { + HandoverGroupDetailVo( + projectCode = it.projectCode, + iamGroupId = it.relationId, + groupName = it.groupName, + groupDesc = it.description, + resourceCode = it.resourceCode, + resourceName = it.resourceName + ) + } + return SQLPage(count = invalidGroupIds.size.toLong(), records = records) + } + + private fun handleHanoverCheck( + request: HandoverOverviewUpdateReq, + overview: HandoverOverviewVo + ) { + if (overview.handoverStatus != HandoverStatus.PENDING) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_FINISH) + } + if (request.handoverAction == HandoverAction.REVOKE && request.operator != overview.applicant) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_REVOKE) + } + if (request.handoverAction != HandoverAction.REVOKE && request.operator != overview.approver) { + throw ErrorCodeException(errorCode = ERROR_HANDOVER_APPROVAL) + } + } + + private fun handleHandoverAgreeAction( + request: HandoverOverviewUpdateReq, + overview: HandoverOverviewVo + ) { + val handoverDetails = permissionHandoverApplicationService.listHandoverDetails( + projectCode = overview.projectCode, + flowNo = overview.flowNo + ) + val handoverType2Records = handoverDetails.groupBy { it.handoverType } + + // 交接用户组 + val groupsOfHandover = handoverType2Records[HandoverType.GROUP]?.map { it.itemId.toInt() } + if (!groupsOfHandover.isNullOrEmpty()) { + val targetMember = ResourceMemberInfo( + id = overview.applicant, + name = deptService.getMemberInfo(overview.applicant, ManagerScopesEnum.USER).displayName, + type = MemberType.USER.type + ) + val handoverTo = ResourceMemberInfo( + id = overview.approver, + name = deptService.getMemberInfo(overview.approver, ManagerScopesEnum.USER).displayName, + type = MemberType.USER.type + ) + + val groupMemberHandoverConditionReq = GroupMemberHandoverConditionReq( + groupIds = groupsOfHandover.map { + MemberGroupJoinedDTO( + id = it, + memberType = MemberType.USER + ) + }, + targetMember = targetMember, + handoverTo = handoverTo + ) + batchOperateGroupMembers( + projectCode = overview.projectCode, + type = BatchOperateType.HANDOVER, + conditionReq = groupMemberHandoverConditionReq, + operateGroupMemberTask = ::handoverTask + ) + } + + // 交接授权 + val authorizationsOfHandover = handoverType2Records[HandoverType.AUTHORIZATION] + if (!authorizationsOfHandover.isNullOrEmpty()) { + val resourceType2Authorizations = authorizationsOfHandover.groupBy { it.resourceType } + resourceType2Authorizations.forEach { (resourceType, authorizations) -> + permissionAuthorizationService.resetResourceAuthorizationByResourceType( + operator = request.operator, + projectCode = overview.projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = overview.projectCode, + resourceType = resourceType, + filterResourceCodes = authorizations.map { it.itemId }, + fullSelection = true, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = overview.applicant, + handoverTo = overview.approver, + checkPermission = false + ) + ) + } + } + } + + private fun batchOperateGroupMembers( + projectCode: String, + conditionReq: T, + type: BatchOperateType, + operateGroupMemberTask: ( + projectCode: String, + groupId: Int, + conditionReq: T, + expiredAt: Long + ) -> Unit + ): Boolean { + val startEpoch = System.currentTimeMillis() + try { + // 成员直接加入的组 + val groupIds = getGroupIdsByGroupMemberCondition( + projectCode = projectCode, + commonCondition = conditionReq + )[MemberType.get(conditionReq.targetMember.type)] + if (groupIds.isNullOrEmpty()) { + return true + } + + val targetMember = conditionReq.targetMember + val memberGroupsDetailsList = listMemberGroupsDetails( + projectCode = projectCode, + memberId = targetMember.id, + memberType = targetMember.type, + groupIds = groupIds + ) + val outOfSyncGroupIds = mutableListOf() + val futures = groupIds.map { groupId -> + CompletableFuture.supplyAsync( + { + val memberGroupsDetails = memberGroupsDetailsList.firstOrNull { it.id == groupId } + if (memberGroupsDetails == null) { + logger.warn("The data is out of sync, and the record no longer exists in the iam.$groupId") + outOfSyncGroupIds.add(groupId) + return@supplyAsync + } + val expiredAt = memberGroupsDetails.expiredAt + RetryUtils.retry(3) { + operateGroupMemberTask.invoke( + projectCode, + groupId, + conditionReq, + expiredAt + ) + } + }, executorService + ) + } + handleFutures( + projectCode = projectCode, + outOfSyncGroupIds = outOfSyncGroupIds, + futures = futures + ) + } finally { + "It take(${System.currentTimeMillis() - startEpoch})ms to $type group members|$projectCode|$conditionReq" + } + return true + } + + private fun listMemberGroupsDetails( + projectCode: String, + memberId: String, + memberType: String, + groupIds: List + ): List { + val memberGroupsDetailsList = mutableListOf() + val groupIdsChunk = groupIds.chunked(100) + val futures = groupIdsChunk.map { + CompletableFuture.supplyAsync( + { + memberGroupsDetailsList.addAll( + // 若离职,则从数据库获取用户加入组的过期时间,调用iam接口会报错。 + // 虽然数据库的过期时间可能不是最新的。 + if (memberType == MemberType.USER.type && deptService.isUserDeparted(memberId)) { + val records = authResourceGroupMemberDao.listMemberGroupDetail( + dslContext = dslContext, + projectCode = projectCode, + memberId = memberId, + iamTemplateIds = emptyList(), + iamGroupIds = it + ) + records.map { record -> + MemberGroupDetailsResponse().apply { + id = record.iamGroupId + expiredAt = record.expiredTime.timestamp() + } + } + } else { + iamV2ManagerService.listMemberGroupsDetails( + memberType, + memberId, + it.joinToString(",") + ) + } + ) + }, executorService + ) + } + try { + CompletableFuture.allOf(*futures.toTypedArray()).join() + } catch (ignore: Exception) { + logger.warn("list member groups details failed!$ignore") + throw ignore + } + return memberGroupsDetailsList + } + + private fun handleFutures( + projectCode: String, + outOfSyncGroupIds: List, + futures: List> + ) { + try { + CompletableFuture.allOf(*futures.toTypedArray()).join() + // 存在iam那边已经把用户组下成员删除,但蓝盾数据库未同步问题 + outOfSyncGroupIds.forEach { + syncIamGroupMemberService.syncIamGroupMember( + projectCode = projectCode, + iamGroupId = it + ) + } + } catch (ignore: Exception) { + logger.warn("batch operate group members failed", ignore) + throw ErrorCodeException( + errorCode = AuthMessageCode.ERROR_BATCH_OPERATE_GROUP_MEMBERS + ) + } + } + + private val url = "${config.devopsHostGateway}/console/permission/my-handover?type=handoverFromMe&flowNo=%s" + + companion object { + private val logger = LoggerFactory.getLogger(RbacPermissionResourceMemberService::class.java) + + private val executorService = Executors.newFixedThreadPool(30) + + // 永久过期时间 + private const val PERMANENT_EXPIRED_TIME = 4102444800000L + + private const val HANDOVER_APPLICATION_RESULT_TEMPLATE_CODE = "BK_PERMISSIONS_HANDOVER_APPLICATION_RESULT" + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionProjectService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionProjectService.kt index b903ad366a36..bd382b7677c4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionProjectService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionProjectService.kt @@ -32,6 +32,7 @@ import com.tencent.bk.sdk.iam.helper.AuthHelper import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.vo.ProjectPermissionInfoVO +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionProjectService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.exception.ErrorCodeException @@ -53,10 +54,11 @@ class RbacPermissionProjectService( private val authResourceService: AuthResourceService, private val authResourceGroupDao: AuthResourceGroupDao, private val dslContext: DSLContext, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val resourceGroupMemberService: RbacPermissionResourceMemberService, private val client: Client, - private val resourceMemberService: PermissionResourceMemberService + private val resourceMemberService: PermissionResourceMemberService, + private val permissionManageFacadeService: PermissionManageFacadeService ) : PermissionProjectService { companion object { @@ -137,7 +139,7 @@ class RbacPermissionProjectService( return managerPermission } - return rbacCacheService.validateUserProjectPermission( + return rbacCommonService.validateUserProjectPermission( userId = userId, projectCode = projectCode, permission = AuthPermission.VISIT @@ -149,17 +151,32 @@ class RbacPermissionProjectService( } } + override fun isProjectMember( + userId: String, + projectCode: String + ): Boolean { + return permissionManageFacadeService.isProjectMember( + projectCode = projectCode, + userId = userId + ) + } + override fun checkUserInProjectLevelGroup(userId: String, projectCode: String): Boolean { - return resourceGroupMemberService.getResourceGroupMembers( + // todo 下个迭代改回 +// return resourceGroupMemberService.getResourceGroupMembers( +// projectCode = projectCode, +// resourceType = AuthResourceType.PROJECT.value, +// resourceCode = projectCode, +// group = null +// ).contains(userId) + return permissionManageFacadeService.isProjectMember( projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - resourceCode = projectCode, - group = null - ).contains(userId) + userId = userId + ) } override fun checkProjectManager(userId: String, projectCode: String): Boolean { - return rbacCacheService.checkProjectManager(userId, projectCode) + return rbacCommonService.checkProjectManager(userId, projectCode) } override fun createProjectUser(userId: String, projectCode: String, roleCode: String): Boolean { diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt deleted file mode 100644 index 79229b6946b4..000000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupAndMemberFacadeServiceImpl.kt +++ /dev/null @@ -1,423 +0,0 @@ -package com.tencent.devops.auth.provider.rbac.service - -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum -import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse -import com.tencent.bk.sdk.iam.service.v2.V2ManagerService -import com.tencent.devops.auth.constant.AuthI18nConstants -import com.tencent.devops.auth.dao.AuthResourceGroupDao -import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao -import com.tencent.devops.auth.pojo.AuthResourceGroupMember -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO -import com.tencent.devops.auth.pojo.enum.JoinedType -import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.DeptService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupService -import com.tencent.devops.auth.service.iam.PermissionResourceMemberService -import com.tencent.devops.common.api.model.SQLPage -import com.tencent.devops.common.api.util.DateTimeUtil -import com.tencent.devops.common.api.util.timestampmilli -import com.tencent.devops.common.auth.api.AuthResourceType -import com.tencent.devops.common.web.utils.I18nUtil -import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord -import org.jooq.DSLContext -import org.slf4j.LoggerFactory -import java.util.concurrent.TimeUnit - -class RbacPermissionResourceGroupAndMemberFacadeServiceImpl( - private val permissionResourceGroupService: PermissionResourceGroupService, - private val groupPermissionService: PermissionResourceGroupPermissionService, - private val permissionResourceMemberService: PermissionResourceMemberService, - private val authResourceGroupDao: AuthResourceGroupDao, - private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, - private val dslContext: DSLContext, - private val deptService: DeptService, - private val iamV2ManagerService: V2ManagerService, - private val rbacCacheService: RbacCacheService -) : PermissionResourceGroupAndMemberFacadeService { - override fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String?, - start: Int?, - limit: Int? - ): SQLPage { - // 根据查询条件查询得到iam组id - val iamGroupIdsByConditions = listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = projectId, - groupName = groupName, - iamGroupIds = iamGroupIds, - relatedResourceType = relatedResourceType, - relatedResourceCode = relatedResourceCode, - action = action - ) - ) - // 查询成员所在资源用户组列表,直接加入+通过用户组(模板)加入 - val (count, resourceGroupMembers) = permissionResourceMemberService.listResourceGroupMembers( - projectCode = projectId, - memberId = memberId, - resourceType = resourceType, - iamGroupIds = iamGroupIdsByConditions, - minExpiredAt = minExpiredAt, - maxExpiredAt = maxExpiredAt, - start = start, - limit = limit - ) - // 用户组对应的资源信息 - val resourceGroupMap = authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectId, - iamGroupIds = resourceGroupMembers.map { it.iamGroupId.toString() } - ).associateBy { it.relationId } - // 只有一个成员的管理员组 - val uniqueManagerGroups = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectId, - iamGroupIds = resourceGroupMembers.map { it.iamGroupId } - ) - // 用户组成员详情 - val groupMemberDetailMap = getGroupMemberDetailMap( - memberId = memberId, - resourceGroupMembers = resourceGroupMembers - ) - val records = mutableListOf() - resourceGroupMembers.forEach { - val resourceGroup = resourceGroupMap[it.iamGroupId.toString()]!! - val groupMemberDetail = groupMemberDetailMap["${it.iamGroupId}_${it.memberId}"] - records.add( - convertGroupDetailsInfoVo( - resourceGroup = resourceGroup, - groupMemberDetail = groupMemberDetail, - uniqueManagerGroups = uniqueManagerGroups, - authResourceGroupMember = it - ) - ) - } - return SQLPage(count = count, records = records) - } - - private fun getGroupMemberDetailMap( - memberId: String, - resourceGroupMembers: List - ): Map { - // 如果用户离职,查询权限中心接口会报错 - if (deptService.isUserDeparted(memberId)) { - return emptyMap() - } - // 用户组成员详情 - val groupMemberDetailMap = mutableMapOf() - // 直接加入的用户 - val userGroupIds = resourceGroupMembers - .filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) } - .map { it.iamGroupId } - if (userGroupIds.isNotEmpty()) { - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.USER), - memberId, - userGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$memberId"] = it - } - } - // 直接加入的组织 - val deptGroupIds = resourceGroupMembers - .filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) } - .map { it.iamGroupId } - if (deptGroupIds.isNotEmpty()) { - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT), - memberId, - deptGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$memberId"] = it - } - } - // 人员模板加入的组 - resourceGroupMembers.filter { it.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) } - .groupBy({ it.memberId }, { it.iamGroupId.toString() }) - .forEach { (iamTemplateId, iamGroupIds) -> - if (iamGroupIds.isEmpty()) return@forEach - iamV2ManagerService.listMemberGroupsDetails( - ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE), - iamTemplateId, - iamGroupIds.joinToString(",") - ).forEach { - groupMemberDetailMap["${it.id}_$iamTemplateId"] = it - } - } - return groupMemberDetailMap - } - - private fun convertGroupDetailsInfoVo( - resourceGroup: TAuthResourceGroupRecord, - groupMemberDetail: MemberGroupDetailsResponse?, - uniqueManagerGroups: List, - authResourceGroupMember: AuthResourceGroupMember - ): GroupDetailsInfoVo { - // 如果用户离职,查询权限中心接口会报错,因此从数据库直接取数据,而不去调用权限中心接口。 - val (expiredAt, joinedTime) = if (groupMemberDetail != null) { - Pair( - TimeUnit.SECONDS.toMillis(groupMemberDetail.expiredAt), - TimeUnit.SECONDS.toMillis(groupMemberDetail.createdAt) - ) - } else { - Pair( - authResourceGroupMember.expiredTime.timestampmilli(), - 0L - ) - } - val between = expiredAt - System.currentTimeMillis() - return GroupDetailsInfoVo( - resourceCode = resourceGroup.resourceCode, - resourceName = resourceGroup.resourceName, - resourceType = resourceGroup.resourceType, - groupId = resourceGroup.relationId.toInt(), - groupName = resourceGroup.groupName, - groupDesc = resourceGroup.description, - expiredAtDisplay = when { - expiredAt == PERMANENT_EXPIRED_TIME -> - I18nUtil.getCodeLanMessage(messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_PERMANENT) - - between >= 0 -> I18nUtil.getCodeLanMessage( - messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_NORMAL, - params = arrayOf(DateTimeUtil.formatDay(between)) - ) - - else -> I18nUtil.getCodeLanMessage( - messageCode = AuthI18nConstants.BK_MEMBER_EXPIRED_AT_DISPLAY_EXPIRED - ) - }, - expiredAt = expiredAt, - joinedTime = joinedTime, - removeMemberButtonControl = when { - authResourceGroupMember.memberType == ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) -> - RemoveMemberButtonControl.TEMPLATE - - resourceGroup.resourceType == AuthResourceType.PROJECT.value && - uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> - RemoveMemberButtonControl.UNIQUE_MANAGER - - uniqueManagerGroups.contains(authResourceGroupMember.iamGroupId) -> - RemoveMemberButtonControl.UNIQUE_OWNER - - else -> - RemoveMemberButtonControl.OTHER - }, - joinedType = when (authResourceGroupMember.memberType) { - ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) -> JoinedType.TEMPLATE - else -> JoinedType.DIRECT - }, - operator = "" - ) - } - - override fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List { - // 查询项目下包含该成员的组列表 - val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( - dslContext = dslContext, - projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - memberId = memberId - ).map { it.iamGroupId.toString() } - // 通过项目组ID获取人员模板ID - val iamTemplateId = authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = projectGroupIds - ).filter { it.iamTemplateId != null } - .map { it.iamTemplateId.toString() } - - val iamGroupIdsByConditions = listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = projectCode, - groupName = groupName, - relatedResourceType = relatedResourceType, - relatedResourceCode = relatedResourceCode, - action = action - ) - ) - // 获取成员直接加入的组和通过模板加入的组 - val memberGroupCountMap = authResourceGroupMemberDao.countMemberGroup( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateId, - iamGroupIds = iamGroupIdsByConditions, - minExpiredAt = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, - maxExpiredAt = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - ) - val memberGroupCountList = mutableListOf() - // 项目排在第一位 - memberGroupCountMap[AuthResourceType.PROJECT.value]?.let { projectCount -> - memberGroupCountList.add( - MemberGroupCountWithPermissionsVo( - resourceType = AuthResourceType.PROJECT.value, - resourceTypeName = I18nUtil.getCodeLanMessage( - messageCode = AuthResourceType.PROJECT.value + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX - ), - count = projectCount - ) - ) - } - - rbacCacheService.listResourceTypes() - .filter { it.resourceType != AuthResourceType.PROJECT.value } - .forEach { resourceTypeInfoVo -> - memberGroupCountMap[resourceTypeInfoVo.resourceType]?.let { count -> - val memberGroupCount = MemberGroupCountWithPermissionsVo( - resourceType = resourceTypeInfoVo.resourceType, - resourceTypeName = I18nUtil.getCodeLanMessage( - messageCode = resourceTypeInfoVo.resourceType + AuthI18nConstants.RESOURCE_TYPE_NAME_SUFFIX, - defaultMessage = resourceTypeInfoVo.name - ), - count = count - ) - memberGroupCountList.add(memberGroupCount) - } - } - - return memberGroupCountList - } - - override fun listIamGroupIdsByConditions(condition: IamGroupIdsQueryConditionDTO): List { - return with(condition) { - val filterGroupsByGroupName = if (isQueryByGroupName()) { - permissionResourceGroupService.listIamGroupIdsByGroupName( - projectId = projectCode, - groupName = groupName!! - ) - } else { - emptyList() - } - val finalGroupIds = if (isQueryByGroupPermissions()) { - groupPermissionService.listGroupsByPermissionConditions( - projectCode = projectCode, - filterIamGroupIds = filterGroupsByGroupName, - relatedResourceType = relatedResourceType!!, - relatedResourceCode = relatedResourceCode, - action = action - ) - } else { - filterGroupsByGroupName - }.toMutableList() - iamGroupIds?.let { finalGroupIds.addAll(it) } - finalGroupIds - } - } - - override fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage { - logger.info("list project members by complex conditions: $conditionReq") - // 不允许同时查询部门名称和用户名称 - if (conditionReq.userName != null && conditionReq.deptName != null) { - return SQLPage(count = 0, records = emptyList()) - } - - // 简单查询直接返回结果 - if (!conditionReq.isComplexQuery()) { - return permissionResourceMemberService.listProjectMembers( - projectCode = conditionReq.projectCode, - memberType = conditionReq.memberType, - userName = conditionReq.userName, - deptName = conditionReq.deptName, - departedFlag = conditionReq.departedFlag, - page = conditionReq.page, - pageSize = conditionReq.pageSize - ) - } - - // 处理复杂查询条件 - val iamGroupIdsByCondition = if (conditionReq.isNeedToQueryIamGroups()) { - listIamGroupIdsByConditions( - condition = IamGroupIdsQueryConditionDTO( - projectCode = conditionReq.projectCode, - groupName = conditionReq.groupName, - relatedResourceType = conditionReq.relatedResourceType, - relatedResourceCode = conditionReq.relatedResourceCode, - action = conditionReq.action - ) - ) - } else { - emptyList() - }.toMutableList() - - // 查询不到用户组,直接返回空 - if (conditionReq.isNeedToQueryIamGroups() && iamGroupIdsByCondition.isEmpty()) { - return SQLPage(0, emptyList()) - } - - val conditionDTO = ProjectMembersQueryConditionDTO.build(conditionReq, iamGroupIdsByCondition) - - if (iamGroupIdsByCondition.isNotEmpty()) { - logger.debug("iamGroupIdsByCondition :{}", iamGroupIdsByCondition) - // 根据用户组Id查询出对应用户组中的人员模板成员 - val iamTemplateIds = authResourceGroupMemberDao.listProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = ProjectMembersQueryConditionDTO( - projectCode = conditionDTO.projectCode, - queryTemplate = true, - iamGroupIds = conditionDTO.iamGroupIds - ) - ) - if (iamTemplateIds.isNotEmpty()) { - // 根据查询出的人员模板ID,查询出对应的组ID - val iamGroupIdsFromTemplate = authResourceGroupDao.listIamGroupIdsByConditions( - dslContext = dslContext, - projectCode = conditionDTO.projectCode, - iamTemplateIds = iamTemplateIds.map { it.id.toInt() } - ) - iamGroupIdsByCondition.addAll(iamGroupIdsFromTemplate) - logger.debug("iamGroupIdsByCondition and template :{}", iamGroupIdsByCondition) - } - } - - val records = authResourceGroupMemberDao.listProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = conditionDTO - ) - logger.debug("listProjectMembersByComplexConditions :{}", records) - - val count = authResourceGroupMemberDao.countProjectMembersByComplexConditions( - dslContext = dslContext, - conditionDTO = conditionDTO - ) - logger.debug("listProjectMembersByComplexConditions :$count") - // 添加离职标志 - return if (conditionDTO.departedFlag == false) { - SQLPage(count, records) - } else { - SQLPage(count, permissionResourceMemberService.addDepartedFlagToMembers(records)) - } - } - - companion object { - private val logger = LoggerFactory.getLogger(RbacPermissionResourceMemberService::class.java) - - // 永久过期时间 - private const val PERMANENT_EXPIRED_TIME = 4102444800000L - } -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt index eb461b5bbb52..2d584b29ad48 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupPermissionService.kt @@ -71,7 +71,7 @@ import java.util.concurrent.Executors @Suppress("LongParameterList") class RbacPermissionResourceGroupPermissionService( private val v2ManagerService: V2ManagerService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val monitorSpaceService: AuthMonitorSpaceService, private val authResourceGroupDao: AuthResourceGroupDao, private val dslContext: DSLContext, @@ -251,7 +251,7 @@ class RbacPermissionResourceGroupPermissionService( action: String? ): List { val resourceType = if (action != null) { - rbacCacheService.getActionInfo(action).relatedResourceType + rbacCommonService.getActionInfo(action).relatedResourceType } else { relatedResourceType } @@ -278,7 +278,9 @@ class RbacPermissionResourceGroupPermissionService( relatedResourceCode: String, action: String ): Boolean { - val resourceType = rbacCacheService.getActionInfo(action).relatedResourceType + if (filterIamGroupIds.isEmpty()) + return false + val resourceType = rbacCommonService.getActionInfo(action).relatedResourceType val pipelineGroupIds = listPipelineGroupIds( projectCode = projectCode, resourceType = resourceType, @@ -295,13 +297,32 @@ class RbacPermissionResourceGroupPermissionService( ) } + override fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean { + if (filterIamGroupIds.isEmpty()) + return false + val actionRelatedResourceType = rbacCommonService.getActionInfo(action).relatedResourceType + return resourceGroupPermissionDao.isGroupsHasProjectLevelPermission( + dslContext = dslContext, + projectCode = projectCode, + filterIamGroupIds = filterIamGroupIds, + actionRelatedResourceType = actionRelatedResourceType, + action = action + ) + } + override fun listGroupResourcesWithPermission( projectCode: String, filterIamGroupIds: List, relatedResourceType: String, action: String ): Map> { - val resourceType = rbacCacheService.getActionInfo(action).relatedResourceType + if (filterIamGroupIds.isEmpty()) + return emptyMap() + val resourceType = rbacCommonService.getActionInfo(action).relatedResourceType return resourceGroupPermissionDao.listGroupResourcesWithPermission( dslContext = dslContext, projectCode = projectCode, @@ -375,7 +396,7 @@ class RbacPermissionResourceGroupPermissionService( val (actionName, actionRelatedResourceType) = if (iamSystemId == monitorSystemId) { Pair(monitorSpaceService.getMonitorActionName(action = actionId), monitorSystemId) } else { - val actionInfo = rbacCacheService.getActionInfo(action = actionId) + val actionInfo = rbacCommonService.getActionInfo(action = actionId) Pair(actionInfo.actionName, actionInfo.relatedResourceType) } GroupPermissionDetailVo( @@ -390,7 +411,7 @@ class RbacPermissionResourceGroupPermissionService( private fun buildRelatedResourceTypesName(iamSystemId: String, instancesDTO: InstancesDTO) { instancesDTO.let { val resourceTypeName = if (iamSystemId == systemId) { - rbacCacheService.getResourceTypeInfo(it.type).name + rbacCommonService.getResourceTypeInfo(it.type).name } else { I18nUtil.getCodeLanMessage(AuthI18nConstants.BK_MONITOR_SPACE) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt index 292dba02ec5b..87aaf9a7c08f 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupService.kt @@ -28,7 +28,6 @@ package com.tencent.devops.auth.provider.rbac.service -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.V2PageInfoDTO import com.tencent.bk.sdk.iam.dto.manager.ManagerRoleGroup import com.tencent.bk.sdk.iam.dto.manager.dto.ManagerRoleGroupDTO @@ -50,6 +49,7 @@ import com.tencent.devops.auth.pojo.dto.GroupAddDTO import com.tencent.devops.auth.pojo.dto.ListGroupConditionDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO import com.tencent.devops.auth.pojo.enum.GroupMemberStatus +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.pojo.request.CustomGroupCreateReq import com.tencent.devops.auth.pojo.vo.IamGroupInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupMemberInfoVo @@ -181,8 +181,8 @@ class RbacPermissionResourceGroupService @Autowired constructor( dslContext = dslContext, projectCode = condition.projectId ) - val userCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.USER)] ?: 0 - val departmentCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT)] ?: 0 + val userCount = projectMemberCount[MemberType.USER.type] ?: 0 + val departmentCount = projectMemberCount[MemberType.DEPARTMENT.type] ?: 0 val allProjectMemberGroup = IamGroupInfoVo( managerId = managerId, defaultGroup = true, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt index 29bb6bfdb0e1..dd986dbebbda 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt @@ -40,6 +40,8 @@ import com.tencent.devops.auth.pojo.AuthResourceGroup import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.enum.ApplyToGroupStatus import com.tencent.devops.auth.pojo.enum.AuthMigrateStatus +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.service.DeptService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService import com.tencent.devops.auth.service.lock.SyncGroupAndMemberLock @@ -73,11 +75,12 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( private val authResourceGroupDao: AuthResourceGroupDao, private val iamV2ManagerService: V2ManagerService, private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val redisOperation: RedisOperation, private val authResourceSyncDao: AuthResourceSyncDao, private val authResourceGroupApplyDao: AuthResourceGroupApplyDao, - private val resourceGroupPermissionService: PermissionResourceGroupPermissionService + private val resourceGroupPermissionService: PermissionResourceGroupPermissionService, + private val deptService: DeptService ) : PermissionResourceGroupSyncService { companion object { private val logger = LoggerFactory.getLogger(RbacPermissionResourceGroupSyncService::class.java) @@ -107,12 +110,14 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( } override fun syncGroupMemberExpiredTime(projectConditionDTO: ProjectConditionDTO) { - logger.info("start to sync group member expired time|$projectConditionDTO") - val traceId = MDC.get(TraceTag.BIZID) - syncExecutorService.submit { + val startEpoch = System.currentTimeMillis() + try { + logger.info("start to sync group member expired time|$projectConditionDTO") + val traceId = MDC.get(TraceTag.BIZID) MDC.put(TraceTag.BIZID, traceId) var offset = 0 val limit = PageUtil.MAX_PAGE_SIZE / 2 + val futures = mutableListOf>() do { val projectCodes = client.get(ServiceProjectResource::class).listProjectsByCondition( projectConditionDTO = projectConditionDTO, @@ -120,33 +125,61 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( offset = offset ).data?.map { it.englishName } ?: break projectCodes.forEach { projectCode -> - syncMemberExpiredExecutorService.submit { - logger.info("start to sync project group member expired time|$projectCode") - val projectMembersOfExpired = authResourceGroupMemberDao.listResourceGroupMember( + futures.add( + CompletableFuture.supplyAsync( + { + MDC.put(TraceTag.BIZID, traceId) + try { + syncGroupMemberExpiredTime(projectCode) + } catch (ex: Exception) { + logger.warn("sync project group member expired time failed $projectCode|$ex") + } + }, + syncMemberExpiredExecutorService + ) + ) + } + offset += limit + } while (projectCodes.size == limit) + CompletableFuture.allOf(*futures.toTypedArray()).join() + } finally { + logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync group member expired time") + } + } + + @Suppress("NestedBlockDepth") + private fun syncGroupMemberExpiredTime(projectCode: String) { + logger.info("start to sync project group member expired time|$projectCode") + val projectMembersOfExpired = authResourceGroupMemberDao.listResourceGroupMember( + dslContext = dslContext, + projectCode = projectCode, + maxExpiredTime = LocalDateTime.now() + ) + val memberId2GroupsExpired = projectMembersOfExpired.groupBy { it.memberId } + memberId2GroupsExpired.forEach { (memberId, groupInfos) -> + try { + if (deptService.isUserDeparted(memberId)) { + return@forEach + } + val verifyResults = iamV2ManagerService.verifyGroupValidMember( + memberId, + groupInfos.joinToString(",") { it.iamGroupId.toString() } + ) + verifyResults.forEach { (groupId, verifyResult) -> + if (verifyResult.belong == true && verifyResult.expiredAt > LocalDateTime.now().timestamp()) { + logger.info("The member of group needs to be renewed:$projectCode|$groupId|$memberId") + authResourceGroupMemberDao.update( dslContext = dslContext, projectCode = projectCode, - maxExpiredTime = LocalDateTime.now() + iamGroupId = groupId, + expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(verifyResult.expiredAt), + memberId = memberId ) - val memberId2GroupsExpired = projectMembersOfExpired.groupBy { it.memberId } - memberId2GroupsExpired.forEach { (memberId, groupInfos) -> - val verifyResults = iamV2ManagerService.verifyGroupValidMember( - memberId, - groupInfos.joinToString(",") { it.iamGroupId.toString() } - ) - verifyResults.forEach { (groupId, verifyResult) -> - if (verifyResult.belong == true && verifyResult.expiredAt > LocalDateTime.now().timestamp()) { - logger.info("The member of group needs to have been renewed:$projectCode|$groupId|$memberId") - syncIamGroupMember( - projectCode = projectCode, - iamGroupId = groupId - ) - } - } - } } } - offset += limit - } while (projectCodes.size == limit) + } catch (ex: Exception) { + logger.warn("sync group member expired time failed!$memberId|$groupInfos|$ex") + } } } @@ -218,62 +251,58 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( } override fun syncIamGroupMembersOfApply() { - val traceId = MDC.get(TraceTag.BIZID) - syncExecutorService.submit { - MDC.put(TraceTag.BIZID, traceId) - val limit = 100 - var offset = 0 - val startEpoch = System.currentTimeMillis() - val finalRecordsOfPending = mutableListOf() - val finalRecordsOfSuccess = mutableListOf() - do { - logger.info("sync members of apply | start") - // 获取7天内未审批单据 - val records = authResourceGroupApplyDao.list( - dslContext = dslContext, - day = 7, - limit = limit, - offset = offset - ) - val (recordsOfSuccess, recordsOfPending) = records.partition { - try { - val isMemberJoinedToGroup = iamV2ManagerService.verifyGroupValidMember( - it.memberId, - it.iamGroupId.toString() - )[it.iamGroupId]?.belong == true - isMemberJoinedToGroup - } catch (ignore: Exception) { - logger.warn("verify group valid member failed,${it.memberId}|${it.iamGroupId}", ignore) - authResourceGroupApplyDao.delete(dslContext, it.id) - false - } + val limit = 100 + var offset = 0 + val startEpoch = System.currentTimeMillis() + val finalRecordsOfPending = mutableListOf() + val finalRecordsOfSuccess = mutableListOf() + do { + logger.info("sync members of apply | start") + // 获取7天内未审批单据 + val records = authResourceGroupApplyDao.list( + dslContext = dslContext, + day = 7, + limit = limit, + offset = offset + ) + val (recordsOfSuccess, recordsOfPending) = records.partition { + try { + val isMemberJoinedToGroup = iamV2ManagerService.verifyGroupValidMember( + it.memberId, + it.iamGroupId.toString() + )[it.iamGroupId]?.belong == true + isMemberJoinedToGroup + } catch (ignore: Exception) { + logger.warn("verify group valid member failed,${it.memberId}|${it.iamGroupId}", ignore) + authResourceGroupApplyDao.delete(dslContext, it.id) + false } - finalRecordsOfPending.addAll(recordsOfPending) - finalRecordsOfSuccess.addAll(recordsOfSuccess) - offset += limit - } while (records.size == limit) - if (finalRecordsOfPending.isNotEmpty()) { - authResourceGroupApplyDao.batchUpdate( - dslContext = dslContext, - ids = finalRecordsOfPending.map { it.id }, - applyToGroupStatus = ApplyToGroupStatus.PENDING - ) } - if (finalRecordsOfSuccess.isNotEmpty()) { - finalRecordsOfSuccess.forEach { - syncIamGroupMember( - projectCode = it.projectCode, - iamGroupId = it.iamGroupId - ) - } - authResourceGroupApplyDao.batchUpdate( - dslContext = dslContext, - ids = finalRecordsOfSuccess.map { it.id }, - applyToGroupStatus = ApplyToGroupStatus.SUCCEED + finalRecordsOfPending.addAll(recordsOfPending) + finalRecordsOfSuccess.addAll(recordsOfSuccess) + offset += limit + } while (records.size == limit) + if (finalRecordsOfPending.isNotEmpty()) { + authResourceGroupApplyDao.batchUpdate( + dslContext = dslContext, + ids = finalRecordsOfPending.map { it.id }, + applyToGroupStatus = ApplyToGroupStatus.PENDING + ) + } + if (finalRecordsOfSuccess.isNotEmpty()) { + finalRecordsOfSuccess.forEach { + syncIamGroupMember( + projectCode = it.projectCode, + iamGroupId = it.iamGroupId ) } - logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync members of apply") + authResourceGroupApplyDao.batchUpdate( + dslContext = dslContext, + ids = finalRecordsOfSuccess.map { it.id }, + applyToGroupStatus = ApplyToGroupStatus.SUCCEED + ) } + logger.info("It take(${System.currentTimeMillis() - startEpoch})ms to sync members of apply") } override fun syncGroupAndMember(projectCode: String) { @@ -477,7 +506,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( val startEpoch = System.currentTimeMillis() logger.info("start to sync resource group member:$projectCode") try { - val resourceTypes = rbacCacheService.listResourceTypes().map { it.resourceType } + val resourceTypes = rbacCommonService.listResourceTypes().map { it.resourceType } val traceId = MDC.get(TraceTag.BIZID) val resourceTypeFuture = resourceTypes.map { resourceType -> CompletableFuture.supplyAsync( @@ -724,7 +753,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( iamGroupId = iamGroupId, memberId = iamGroupTemplate.id, memberName = iamGroupTemplate.name, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE), + memberType = MemberType.TEMPLATE.type, expiredTime = expiredTime ) ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt index 401e717b46f0..b0ad89bd2453 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt @@ -8,7 +8,6 @@ import com.tencent.bk.sdk.iam.dto.manager.V2ManagerRoleGroupInfo import com.tencent.bk.sdk.iam.dto.manager.dto.GroupMemberRenewApplicationDTO import com.tencent.bk.sdk.iam.dto.manager.dto.ManagerMemberGroupDTO import com.tencent.bk.sdk.iam.dto.manager.dto.SearchGroupDTO -import com.tencent.bk.sdk.iam.dto.response.MemberGroupDetailsResponse import com.tencent.bk.sdk.iam.service.v2.V2ManagerService import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthResourceGroupDao @@ -16,36 +15,22 @@ import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType import com.tencent.devops.auth.pojo.enum.MemberType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.auth.service.DeptService -import com.tencent.devops.auth.service.PermissionAuthorizationService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.util.DateTimeUtil import com.tencent.devops.common.api.util.PageUtil -import com.tencent.devops.common.api.util.timestamp import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.api.pojo.BkAuthGroup import com.tencent.devops.common.auth.api.pojo.BkAuthGroupAndUserList -import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq -import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest -import com.tencent.devops.common.service.utils.RetryUtils import com.tencent.devops.project.constant.ProjectMessageCode import org.apache.commons.lang3.RandomUtils import org.jooq.DSLContext import org.slf4j.LoggerFactory import java.time.LocalDateTime -import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -56,9 +41,7 @@ class RbacPermissionResourceMemberService( private val authResourceGroupDao: AuthResourceGroupDao, private val authResourceGroupMemberDao: AuthResourceGroupMemberDao, private val dslContext: DSLContext, - private val deptService: DeptService, - private val permissionAuthorizationService: PermissionAuthorizationService, - private val syncIamGroupMemberService: PermissionResourceGroupSyncService + private val deptService: DeptService ) : PermissionResourceMemberService { override fun getResourceGroupMembers( projectCode: String, @@ -145,8 +128,8 @@ class RbacPermissionResourceMemberService( projectCode = projectCode ) return ResourceMemberCountVO( - userCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.USER)] ?: 0, - departmentCount = projectMemberCount[ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT)] ?: 0 + userCount = projectMemberCount[MemberType.USER.type] ?: 0, + departmentCount = projectMemberCount[MemberType.DEPARTMENT.type] ?: 0 ) } @@ -192,7 +175,7 @@ class RbacPermissionResourceMemberService( override fun addDepartedFlagToMembers(records: List): List { val userMembers = records.filter { - it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) + it.type == MemberType.USER.type }.map { it.id } val departedMembers = if (userMembers.isNotEmpty()) { deptService.listDepartedMembers( @@ -202,7 +185,7 @@ class RbacPermissionResourceMemberService( return records } return records.map { - if (it.type != ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { + if (it.type != MemberType.USER.type) { it.copy(departed = false) } else { it.copy(departed = departedMembers.contains(it.id)) @@ -218,7 +201,7 @@ class RbacPermissionResourceMemberService( expiredAt: Long, iamGroupId: Int ): Boolean { - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && + if (memberType == MemberType.USER.type && deptService.isUserDeparted(memberId)) { return true } @@ -285,8 +268,8 @@ class RbacPermissionResourceMemberService( iamGroupId = iamGroupId ) // 获取用户组中用户以及部门 - val userType = ManagerScopesEnum.getType(ManagerScopesEnum.USER) - val deptType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) + val userType = MemberType.USER.type + val deptType = MemberType.DEPARTMENT.type val pageInfoDTO = V2PageInfoDTO().apply { pageSize = 1000 page = 1 @@ -421,8 +404,8 @@ class RbacPermissionResourceMemberService( projectCode = projectCode, iamGroupId = iamGroupId ) - val userType = ManagerScopesEnum.getType(ManagerScopesEnum.USER) - val deptType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) + val userType = MemberType.USER.type + val deptType = MemberType.DEPARTMENT.type val allMemberIds = mutableListOf() if (!members.isNullOrEmpty()) { deleteIamGroupMembers( @@ -486,7 +469,7 @@ class RbacPermissionResourceMemberService( val nowTimestamp = System.currentTimeMillis() / 1000 val (members, deptInfoList) = groupMemberInfoList .filter { it.expiredAt > nowTimestamp } - .partition { it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) } + .partition { it.type == MemberType.USER.type } return BkAuthGroupAndUserList( displayName = groupInfo.name, @@ -616,25 +599,6 @@ class RbacPermissionResourceMemberService( return true } - override fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean { - logger.info("renewal group member $userId|$projectCode|$renewalConditionReq") - val groupId = renewalConditionReq.groupId - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = GroupMemberRenewalConditionReq( - groupIds = listOf(groupId), - targetMember = renewalConditionReq.targetMember, - renewalDuration = renewalConditionReq.renewalDuration - ), - operateGroupMemberTask = ::renewalTask - ) - return true - } - override fun renewalIamGroupMembers( groupId: Int, members: List, @@ -653,82 +617,12 @@ class RbacPermissionResourceMemberService( return true } - override fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean { - logger.info("batch renewal group member $userId|$projectCode|$renewalConditionReq") - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = renewalConditionReq, - operateGroupMemberTask = ::renewalTask - ) - return true - } - - private fun renewalTask( - projectCode: String, - groupId: Int, - renewalConditionReq: GroupMemberRenewalConditionReq, - expiredAt: Long - ) { - logger.info("renewal group member ${renewalConditionReq.targetMember}|$projectCode|$groupId|$expiredAt") - val targetMember = renewalConditionReq.targetMember - if (targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(targetMember.id)) { - return - } - val secondsOfRenewalDuration = TimeUnit.DAYS.toSeconds(renewalConditionReq.renewalDuration.toLong()) - val secondsOfCurrentTime = System.currentTimeMillis() / 1000 - // 若权限已过期,则为当前时间+续期天数,若未过期,则为有效期+续期天数 - val finalExpiredAt = if (expiredAt < secondsOfCurrentTime) { - secondsOfCurrentTime - } else { - expiredAt - } + secondsOfRenewalDuration - if (!isNeedToRenewal(finalExpiredAt)) { - return - } - renewalIamGroupMembers( - groupId = groupId, - members = listOf(ManagerMember(targetMember.type, targetMember.id)), - expiredAt = finalExpiredAt - ) - authResourceGroupMemberDao.update( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt), - memberId = targetMember.id - ) - } - - private fun isNeedToRenewal(expiredAt: Long): Boolean { - return expiredAt < PERMANENT_EXPIRED_TIME - } - - override fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean { - logger.info("batch delete group members $userId|$projectCode|$removeMemberDTO") - removeMemberDTO.excludedUniqueManagerGroup = true - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = removeMemberDTO, - operateGroupMemberTask = ::deleteTask - ) - return true - } - override fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean { - val membersOfNeedToDelete = if (type == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { + val membersOfNeedToDelete = if (type == MemberType.USER.type) { memberIds.filterNot { deptService.isUserDeparted(it) } } else { memberIds @@ -743,595 +637,15 @@ class RbacPermissionResourceMemberService( return true } - private fun deleteTask( - projectCode: String, - groupId: Int, - removeMemberDTO: GroupMemberCommonConditionReq, - expiredAt: Long - ) { - val targetMember = removeMemberDTO.targetMember - logger.info("delete group member $projectCode|$groupId|$targetMember") - deleteIamGroupMembers( - groupId = groupId, - type = targetMember.type, - memberIds = listOf(targetMember.id) - ) - authResourceGroupMemberDao.batchDeleteGroupMembers( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - memberIds = listOf(removeMemberDTO.targetMember.id) - ) - } - - override fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean { - logger.info("batch handover group members $userId|$projectCode|$handoverMemberDTO") - handoverMemberDTO.checkHandoverTo() - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = handoverMemberDTO, - operateGroupMemberTask = ::handoverTask - ) - return true - } - - override fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo { - logger.info("batch operate group member check|$userId|$projectCode|$batchOperateType|$conditionReq") - // 获取用户加入的用户组 - val (groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) = getGroupIdsByCondition( - projectCode = projectCode, - commonCondition = conditionReq - ) - val totalCount = groupIdsOfDirectJoined.size + groupInfoIdsOfTemplateJoined.size - val groupCountOfTemplateJoined = groupInfoIdsOfTemplateJoined.size - - return when (batchOperateType) { - BatchOperateType.REMOVE -> { - val groupCountOfUniqueManager = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ).size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfUniqueManager + groupCountOfTemplateJoined - ) - } - - BatchOperateType.RENEWAL -> { - with(conditionReq) { - val isUserDeparted = targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(targetMember.id) - // 离职用户不允许续期 - if (isUserDeparted) { - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = totalCount - ) - } else { - // 永久期限 不允许再续期 - val groupCountOfPermanentExpiredTime = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = groupIdsOfDirectJoined - ).filter { - // iam用的是秒级时间戳 - it.expiredAt == PERMANENT_EXPIRED_TIME / 1000 - }.size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfPermanentExpiredTime + groupCountOfTemplateJoined - ) - } - } - } - - BatchOperateType.HANDOVER -> { - // 已过期(除唯一管理员组)或通过模板加入的不允许移交 - with(conditionReq) { - val finalGroupIds = groupIdsOfDirectJoined.toMutableList() - val uniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ) - // 去除唯一管理员组 - if (uniqueManagerGroupIds.isNotEmpty()) { - finalGroupIds.removeAll(uniqueManagerGroupIds) - } - val groupCountOfExpired = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = finalGroupIds - ).filter { - // iam用的是秒级时间戳 - it.expiredAt < System.currentTimeMillis() / 1000 - }.size - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfTemplateJoined + groupCountOfExpired - ) - } - } - - else -> { - BatchOperateGroupMemberCheckVo( - totalCount = totalCount, - inoperableCount = groupCountOfTemplateJoined - ) - } - } - } - - override fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List { - logger.info("remove member from project $userId|$projectCode|$removeMemberFromProjectReq") - return with(removeMemberFromProjectReq) { - val memberType = targetMember.type - val isNeedToHandover = handoverTo != null - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && isNeedToHandover) { - removeMemberFromProjectReq.checkHandoverTo() - val handoverMemberDTO = GroupMemberHandoverConditionReq( - allSelection = true, - targetMember = targetMember, - handoverTo = handoverTo!! - ) - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = handoverMemberDTO, - operateGroupMemberTask = ::handoverTask - ) - permissionAuthorizationService.resetAllResourceAuthorization( - operator = userId, - projectCode = projectCode, - condition = ResetAllResourceAuthorizationReq( - projectCode = projectCode, - handoverFrom = removeMemberFromProjectReq.targetMember.id, - handoverTo = removeMemberFromProjectReq.handoverTo!!.id, - preCheck = false, - checkPermission = false - ) - ) - } else { - val removeMemberDTO = GroupMemberCommonConditionReq( - allSelection = true, - targetMember = targetMember - ) - batchOperateGroupMembers( - projectCode = projectCode, - conditionReq = removeMemberDTO, - operateGroupMemberTask = ::deleteTask - ) - } - - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { - // 查询用户还存在那些组织中 - val userDeptInfos = deptService.getUserInfo( - userId = "admin", - name = targetMember.id - )?.deptInfo?.map { it.name!! } - if (userDeptInfos != null) { - return authResourceGroupMemberDao.isMembersInProject( - dslContext = dslContext, - projectCode = projectCode, - memberNames = userDeptInfos, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT) - ) - } - } - return emptyList() - } - } - - override fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean { - val targetMember = removeMemberFromProjectReq.targetMember - val isMemberHasNoPermission = batchOperateGroupMembersCheck( - userId = userId, - projectCode = projectCode, - batchOperateType = BatchOperateType.HANDOVER, - conditionReq = GroupMemberCommonConditionReq( - allSelection = true, - targetMember = removeMemberFromProjectReq.targetMember - ) - ).let { it.totalCount == it.inoperableCount } - - val isMemberHasNoAuthorizations = - if (targetMember.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { - permissionAuthorizationService.listResourceAuthorizations( - condition = ResourceAuthorizationConditionRequest( - projectCode = projectCode, - handoverFrom = targetMember.id - ) - ).count == 0L - } else { - true - } - return isMemberHasNoPermission && isMemberHasNoAuthorizations - } - - private fun handoverTask( - projectCode: String, - groupId: Int, - handoverMemberDTO: GroupMemberHandoverConditionReq, - expiredAt: Long - ) { - logger.info( - "handover group member $projectCode|$groupId|" + - "${handoverMemberDTO.targetMember}|${handoverMemberDTO.handoverTo}" - ) - val currentTimeSeconds = System.currentTimeMillis() / 1000 - var finalExpiredAt = expiredAt - when { - // 若权限已过期,如果是唯一管理员组,允许交接,交接人将获得半年权限;其他的直接删除。 - expiredAt < currentTimeSeconds -> { - val isUniqueManagerGroup = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = listOf(groupId) - ).isNotEmpty() - if (isUniqueManagerGroup) { - finalExpiredAt = currentTimeSeconds + TimeUnit.DAYS.toSeconds(180) - } else { - deleteTask( - projectCode = projectCode, - groupId = groupId, - removeMemberDTO = GroupMemberCommonConditionReq( - targetMember = handoverMemberDTO.targetMember - ), - expiredAt = finalExpiredAt - ) - return - } - } - // 若交接人已经在用户组内,无需交接。 - authResourceGroupMemberDao.isMemberInGroup( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - memberId = handoverMemberDTO.handoverTo.id - ) -> { - deleteTask( - projectCode = projectCode, - groupId = groupId, - removeMemberDTO = GroupMemberCommonConditionReq( - targetMember = handoverMemberDTO.targetMember - ), - expiredAt = finalExpiredAt - ) - return - } - } - - val members = listOf( - ManagerMember( - handoverMemberDTO.handoverTo.type, - handoverMemberDTO.handoverTo.id - ) - ) - if (finalExpiredAt < currentTimeSeconds) { - throw ErrorCodeException( - errorCode = AuthMessageCode.INVALID_EXPIRED_PERM_NOT_ALLOW_TO_HANDOVER - ) - } - - addIamGroupMember( - groupId = groupId, - members = members, - expiredAt = finalExpiredAt - ) - deleteIamGroupMembers( - groupId = groupId, - type = handoverMemberDTO.targetMember.type, - memberIds = listOf(handoverMemberDTO.targetMember.id) - ) - authResourceGroupMemberDao.handoverGroupMembers( - dslContext = dslContext, - projectCode = projectCode, - iamGroupId = groupId, - handoverFrom = handoverMemberDTO.targetMember, - handoverTo = handoverMemberDTO.handoverTo, - expiredTime = DateTimeUtil.convertTimestampToLocalDateTime(finalExpiredAt) - ) - } - - private fun batchOperateGroupMembers( - projectCode: String, - conditionReq: T, - operateGroupMemberTask: ( - projectCode: String, - groupId: Int, - conditionReq: T, - expiredAt: Long - ) -> Unit - ): Boolean { - val groupIds = getGroupIdsByCondition( - projectCode = projectCode, - commonCondition = conditionReq - ).first - val targetMember = conditionReq.targetMember - val memberGroupsDetailsList = listMemberGroupsDetails( - projectCode = projectCode, - memberId = targetMember.id, - memberType = targetMember.type, - groupIds = groupIds - ) - val outOfSyncGroupIds = mutableListOf() - val futures = groupIds.map { groupId -> - CompletableFuture.supplyAsync( - { - val memberGroupsDetails = memberGroupsDetailsList.firstOrNull { it.id == groupId } - if (memberGroupsDetails == null) { - logger.warn( - "The data is out of sync, and the record no longer exists in the iam.$groupId" - ) - outOfSyncGroupIds.add(groupId) - return@supplyAsync - } - val expiredAt = memberGroupsDetails.expiredAt - RetryUtils.retry(3) { - operateGroupMemberTask.invoke( - projectCode, - groupId, - conditionReq, - expiredAt - ) - } - }, executorService - ) - } - handleFutures( - projectCode = projectCode, - outOfSyncGroupIds = outOfSyncGroupIds, - futures = futures - ) - return true - } - - private fun handleFutures( - projectCode: String, - outOfSyncGroupIds: List, - futures: List> - ) { - try { - CompletableFuture.allOf(*futures.toTypedArray()).join() - // 存在iam那边已经把用户组下成员删除,但蓝盾数据库未同步问题 - outOfSyncGroupIds.forEach { - syncIamGroupMemberService.syncIamGroupMember( - projectCode = projectCode, - iamGroupId = it - ) - } - } catch (ignore: Exception) { - logger.warn("batch operate group members failed", ignore) - throw ErrorCodeException( - errorCode = AuthMessageCode.ERROR_BATCH_OPERATE_GROUP_MEMBERS - ) - } - } - - private fun getGroupIdsByCondition( - projectCode: String, - commonCondition: GroupMemberCommonConditionReq - ): Pair, List> /*直接加入,模板加入*/ { - val finalResourceGroupMembers = mutableListOf() - with(commonCondition) { - val resourceGroupMembersByCondition = when { - // 全选 - allSelection -> { - listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id - ).second - } - // 全选某些资源类型用户组 - resourceTypes.isNotEmpty() -> { - resourceTypes.flatMap { resourceType -> - listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id, - resourceType = resourceType - ).second - } - } - - else -> { - emptyList() - } - } - - if (resourceGroupMembersByCondition.isNotEmpty()) { - finalResourceGroupMembers.addAll(resourceGroupMembersByCondition) - } - - // Select specific groups individually - if (groupIds.isNotEmpty()) { - val resourceGroupMembersOfSelect = listResourceGroupMembers( - projectCode = projectCode, - memberId = commonCondition.targetMember.id, - iamGroupIds = groupIds - ).second - finalResourceGroupMembers.addAll(resourceGroupMembersOfSelect) - } - - val (groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) = finalResourceGroupMembers.partition { - it.memberType != ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) - }.run { - first.map { it.iamGroupId }.toMutableList() to second.map { it.iamGroupId }.toMutableList() - } - - // When batch removing, if the user is the only manager of the group, ignore and do not transfer - if (excludedUniqueManagerGroup) { - val excludedUniqueManagerGroupIds = authResourceGroupMemberDao.listProjectUniqueManagerGroups( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = groupIdsOfDirectJoined - ) - groupIdsOfDirectJoined.removeAll { - excludedUniqueManagerGroupIds.contains(it) - } - } - return Pair(groupIdsOfDirectJoined, groupInfoIdsOfTemplateJoined) - } - } - - private fun listMemberGroupsDetails( - projectCode: String, - memberId: String, - memberType: String, - groupIds: List - ): List { - val memberGroupsDetailsList = mutableListOf() - val groupIdsChunk = groupIds.chunked(100) - val futures = groupIdsChunk.map { - CompletableFuture.supplyAsync( - { - memberGroupsDetailsList.addAll( - // 若离职,则从数据库获取用户加入组的过期时间,调用iam接口会报错。 - // 虽然数据库的过期时间可能不是最新的。 - if (memberType == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && - deptService.isUserDeparted(userId = memberId)) { - val records = authResourceGroupMemberDao.listMemberGroupDetail( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = emptyList(), - iamGroupIds = it - ) - records.map { record -> - MemberGroupDetailsResponse().apply { - id = record.iamGroupId - expiredAt = record.expiredTime.timestamp() - } - } - } else { - iamV2ManagerService.listMemberGroupsDetails( - memberType, - memberId, - it.joinToString(",") - ) - } - ) - }, executorService - ) - } - try { - CompletableFuture.allOf(*futures.toTypedArray()).join() - } catch (ignore: Exception) { - logger.warn("list member groups details failed!$ignore") - throw ignore - } - return memberGroupsDetailsList - } - - // 查询成员所在资源用户组列表,直接加入+通过用户组(模板)加入 - @Suppress("LongParameterList") - override fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - start: Int?, - limit: Int? - ): Pair> { - // 获取用户加入的项目级用户组模板ID - val iamTemplateIds = listProjectMemberGroupTemplateIds( - projectCode = projectCode, - memberId = memberId - ) - val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } - val count = authResourceGroupMemberDao.countMemberGroup( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds, - resourceType = resourceType, - iamGroupIds = iamGroupIds, - minExpiredAt = minExpiredTime, - maxExpiredAt = maxExpiredTime - )[resourceType] ?: 0L - val resourceGroupMembers = authResourceGroupMemberDao.listMemberGroupDetail( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds, - resourceType = resourceType, - iamGroupIds = iamGroupIds, - minExpiredAt = minExpiredTime, - maxExpiredAt = maxExpiredTime, - offset = start, - limit = limit - ) - return Pair(count, resourceGroupMembers) - } - - override fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List { - // 获取用户加入的项目级用户组模板ID - val iamTemplateIds = listProjectMemberGroupTemplateIds( - projectCode = projectCode, - memberId = memberId - ) - return authResourceGroupMemberDao.listMemberGroupIdsInProject( - dslContext = dslContext, - projectCode = projectCode, - memberId = memberId, - iamTemplateIds = iamTemplateIds - ) - } - - // 获取用户加入的项目级用户组模板ID - private fun listProjectMemberGroupTemplateIds( - projectCode: String, - memberId: String - ): List { - // 查询项目下包含该成员的组列表 - val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( - dslContext = dslContext, - projectCode = projectCode, - resourceType = AuthResourceType.PROJECT.value, - memberId = memberId - ).map { it.iamGroupId.toString() } - // 通过项目组ID获取人员模板ID - return authResourceGroupDao.listByRelationId( - dslContext = dslContext, - projectCode = projectCode, - iamGroupIds = projectGroupIds - ).filter { it.iamTemplateId != null } - .map { it.iamTemplateId.toString() } - } - private fun MutableList.removeDepartedMembers(): List { - val userMemberIds = this.filter { it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) }.map { it.id } + val userMemberIds = this.filter { it.type == MemberType.USER.type }.map { it.id } if (userMemberIds.isEmpty()) return this // 获取离职的人员 val departedMembers = deptService.listDepartedMembers( memberIds = userMemberIds ) return this.filterNot { - it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) && + it.type == MemberType.USER.type && departedMembers.contains(it.id) } } @@ -1349,8 +663,5 @@ class RbacPermissionResourceMemberService( private val AUTO_RENEWAL_EXPIRED_AT = TimeUnit.DAYS.toSeconds(180) private val executorService = Executors.newFixedThreadPool(30) - - // 永久过期时间 - private const val PERMANENT_EXPIRED_TIME = 4102444800000L } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt index c0baf8abee19..709b1320b4a5 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceValidateService.kt @@ -29,7 +29,9 @@ package com.tencent.devops.auth.provider.rbac.service import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.dao.AuthAuthorizationDao import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.exception.ErrorCodeException @@ -37,6 +39,7 @@ import com.tencent.devops.common.api.exception.PermissionForbiddenException import com.tencent.devops.common.api.util.Watcher import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest import com.tencent.devops.common.auth.rbac.utils.RbacAuthUtils import com.tencent.devops.common.client.Client import com.tencent.devops.common.service.utils.LogUtils @@ -44,13 +47,16 @@ import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.project.api.service.ServiceProjectResource import com.tencent.devops.project.constant.ProjectMessageCode import com.tencent.devops.project.pojo.enums.ProjectApproveStatus +import org.jooq.DSLContext import org.slf4j.LoggerFactory import javax.ws.rs.NotFoundException class RbacPermissionResourceValidateService( private val permissionService: PermissionService, - private val rbacCacheService: RbacCacheService, - private val client: Client + private val rbacCommonService: RbacCommonService, + private val client: Client, + private val authAuthorizationDao: AuthAuthorizationDao, + private val dslContext: DSLContext ) : PermissionResourceValidateService { companion object { @@ -69,7 +75,7 @@ class RbacPermissionResourceValidateService( val resourceActionList = mutableSetOf() permissionBatchValidateDTO.actionList.forEach { action -> - val actionInfo = rbacCacheService.getActionInfo(action) + val actionInfo = rbacCommonService.getActionInfo(action) val iamRelatedResourceType = actionInfo.relatedResourceType if (iamRelatedResourceType == AuthResourceType.PROJECT.value) { projectActionList.add(action) @@ -117,7 +123,7 @@ class RbacPermissionResourceValidateService( resourceCode: String ): Boolean { checkProjectApprovalStatus(resourceType, resourceCode) - val checkProjectManage = rbacCacheService.checkProjectManager( + val checkProjectManage = rbacCommonService.checkProjectManager( userId = userId, projectCode = projectId ) @@ -152,6 +158,55 @@ class RbacPermissionResourceValidateService( return true } + override fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) { + if (operateChannel == OperateChannel.PERSONAL) { + // 个人视角校验 + val hasVisitPermission = permissionService.validateUserResourcePermission( + userId = userId, + resourceType = AuthResourceType.PROJECT.value, + action = RbacAuthUtils.buildAction(AuthPermission.VISIT, AuthResourceType.PROJECT), + projectCode = projectCode + ) + if (hasVisitPermission) return + + val isUserHasProjectAuthorizations = authAuthorizationDao.count( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = userId + ) + ) > 0 + if (!isUserHasProjectAuthorizations) { + throw PermissionForbiddenException( + message = "The user does not have permission to visit the project!" + ) + } + if (userId != targetMemberId) { + throw PermissionForbiddenException( + message = "You do not have permission to operate other user groups!" + ) + } + } else { + // 管理员视角校验 + val hasProjectManagePermission = permissionService.validateUserResourcePermission( + userId = userId, + resourceType = AuthResourceType.PROJECT.value, + action = RbacAuthUtils.buildAction(AuthPermission.MANAGE, AuthResourceType.PROJECT), + projectCode = projectCode + ) + if (!hasProjectManagePermission) { + throw PermissionForbiddenException( + message = I18nUtil.getCodeLanMessage(AuthMessageCode.ERROR_AUTH_NO_MANAGE_PERMISSION) + ) + } + } + } + private fun checkProjectApprovalStatus(resourceType: String, resourceCode: String) { if (resourceType == AuthResourceType.PROJECT.value) { val projectInfo = diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt index 08b14499753e..3523cbf55445 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.InstanceDTO import com.tencent.bk.sdk.iam.dto.PathInfoDTO import com.tencent.bk.sdk.iam.dto.SubjectDTO @@ -39,6 +38,7 @@ import com.tencent.bk.sdk.iam.dto.resource.ResourceDTO import com.tencent.bk.sdk.iam.dto.resource.V2ResourceNode import com.tencent.bk.sdk.iam.helper.AuthHelper import com.tencent.bk.sdk.iam.service.PolicyService +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.service.AuthProjectUserMetricsService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.PermissionService @@ -63,7 +63,7 @@ class RbacPermissionService( private val policyService: PolicyService, private val authResourceCodeConverter: AuthResourceCodeConverter, private val superManagerService: SuperManagerService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val client: Client, private val authProjectUserMetricsService: AuthProjectUserMetricsService ) : PermissionService { @@ -94,7 +94,7 @@ class RbacPermissionService( projectCode: String, resourceType: String? ): Boolean { - val actionInfo = rbacCacheService.getActionInfo(action) + val actionInfo = rbacCommonService.getActionInfo(action) // 如果action关联的资源是项目,则直接查询项目的权限 return if (actionInfo.relatedResourceType == AuthResourceType.PROJECT.value) { validateUserResourcePermissionByRelation( @@ -179,7 +179,7 @@ class RbacPermissionService( } ?: return false val subject = SubjectDTO.builder() .id(userId) - .type(ManagerScopesEnum.getType(ManagerScopesEnum.USER)) + .type(MemberType.USER.type) .build() val actionDTO = ActionDTO() @@ -265,7 +265,7 @@ class RbacPermissionService( ) val startEpoch = System.currentTimeMillis() try { - if (rbacCacheService.checkProjectManager(userId = userId, projectCode = projectCode)) { + if (rbacCommonService.checkProjectManager(userId = userId, projectCode = projectCode)) { return actions.associateWith { true } } val actionList = actions.map { action -> @@ -457,7 +457,7 @@ class RbacPermissionService( ) val startEpoch = System.currentTimeMillis() try { - if (rbacCacheService.checkProjectManager(userId = userId, projectCode = projectCode)) { + if (rbacCommonService.checkProjectManager(userId = userId, projectCode = projectCode)) { return actions.associate { val authPermission = it.substringAfterLast("_") AuthPermission.get(authPermission) to resources.map { resource -> resource.resourceCode } @@ -614,7 +614,7 @@ class RbacPermissionService( resourceType: String, action: String ): Boolean { - return rbacCacheService.checkProjectManager( + return rbacCommonService.checkProjectManager( userId = userId, projectCode = projectCode ) || superManagerService.projectManagerCheck( diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt index d74c7192ead2..7311fd7c3b4c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/AbMigratePolicyService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service.migrate import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.manager.AuthorizationScopes import com.tencent.bk.sdk.iam.dto.manager.ManagerPath import com.tencent.bk.sdk.iam.dto.manager.ManagerResources @@ -41,8 +40,9 @@ import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateIamApiService.Companion.GROUP_API_POLICY import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateIamApiService.Companion.GROUP_WEB_POLICY import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateIamApiService.Companion.USER_CUSTOM_POLICY @@ -75,7 +75,7 @@ abstract class AbMigratePolicyService( private val migrateIamApiService: MigrateIamApiService, private val authMigrationDao: AuthMigrationDao, private val permissionService: PermissionService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val deptService: DeptService, private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, private val permissionResourceMemberService: PermissionResourceMemberService @@ -382,7 +382,7 @@ abstract class AbMigratePolicyService( permissionResourceMemberService.addGroupMember( projectCode = projectCode, memberId = userId, - memberType = ManagerScopesEnum.getType(ManagerScopesEnum.USER), + memberType = MemberType.USER.type, expiredAt = System.currentTimeMillis() / MILLISECOND + TimeUnit.DAYS.toSeconds(DEFAULT_EXPIRED_DAY), iamGroupId = groupId @@ -497,7 +497,7 @@ abstract class AbMigratePolicyService( resourceCode: String, userId: String ): Pair { - rbacCacheService.getGroupConfigAction(resourceType).forEach groupConfig@{ groupConfig -> + rbacCommonService.getGroupConfigAction(resourceType).forEach groupConfig@{ groupConfig -> if (groupConfig.actions.containsAll(actions)) { val groupId = authResourceGroupDao.get( dslContext = dslContext, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt index e7597f6ccc14..dd14d20be283 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigratePermissionHandoverService.kt @@ -32,7 +32,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.dto.PermissionHandoverDTO import com.tencent.devops.auth.pojo.enum.JoinedType import com.tencent.devops.auth.provider.rbac.service.AuthResourceService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.common.auth.api.pojo.DefaultGroupType @@ -43,7 +43,7 @@ class MigratePermissionHandoverService( private val permissionResourceMemberService: PermissionResourceMemberService, private val authResourceGroupDao: AuthResourceGroupDao, private val authResourceService: AuthResourceService, - private val resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService, + private val permissionManageFacadeService: PermissionManageFacadeService, private val dslContext: DSLContext ) { fun handoverPermissions(permissionHandoverDTO: PermissionHandoverDTO) { @@ -114,7 +114,7 @@ class MigratePermissionHandoverService( ) } // 交接用户组权限 - val userJoinedGroups = resourceGroupAndMemberFacadeService.getMemberGroupsDetails( + val userJoinedGroups = permissionManageFacadeService.getMemberGroupsDetails( projectId = projectCode, memberId = handoverFrom ).records.filter { it.joinedType == JoinedType.DIRECT }.map { it.groupId } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResourceService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResourceService.kt index 8f931ec5e7ef..a6ddd6c98420 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResourceService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResourceService.kt @@ -42,7 +42,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.pojo.dto.ResourceMigrationCountDTO import com.tencent.devops.auth.provider.rbac.service.AuthResourceService import com.tencent.devops.auth.provider.rbac.service.PermissionGradeManagerService -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.provider.rbac.service.RbacPermissionResourceService import com.tencent.devops.auth.service.ResourceService import com.tencent.devops.auth.service.iam.MigrateCreatorFixService @@ -66,7 +66,7 @@ import java.util.concurrent.Executors @Suppress("LongParameterList", "MagicNumber") class MigrateResourceService @Autowired constructor( private val resourceService: ResourceService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val rbacPermissionResourceService: RbacPermissionResourceService, private val migrateCreatorFixService: MigrateCreatorFixService, private val authResourceService: AuthResourceService, @@ -89,7 +89,7 @@ class MigrateResourceService @Autowired constructor( val startEpoch = System.currentTimeMillis() logger.info("start to migrate resource:$projectCode") try { - val resourceTypes = rbacCacheService.listResourceTypes() + val resourceTypes = rbacCommonService.listResourceTypes() .map { it.resourceType } .filterNot { noNeedToMigrateResourceType.contains(it) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResultService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResultService.kt index 3b2c76b3b23a..b97904e30853 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResultService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateResultService.kt @@ -33,7 +33,7 @@ import com.tencent.devops.auth.constant.AuthMessageCode import com.tencent.devops.auth.provider.rbac.service.AuthResourceService import com.tencent.devops.auth.service.AuthVerifyRecordService import com.tencent.devops.auth.service.DeptService -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.util.PageUtil @@ -55,7 +55,7 @@ import java.util.concurrent.Executors @Suppress("ALL") class MigrateResultService constructor( private val permissionService: PermissionService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val migrateResourceCodeConverter: MigrateResourceCodeConverter, private val authVerifyRecordService: AuthVerifyRecordService, private val migrateResourceService: MigrateResourceService, @@ -79,7 +79,7 @@ class MigrateResultService constructor( logger.info("start to compare policy|$projectCode") val startEpoch = System.currentTimeMillis() try { - val resourceTypes = rbacCacheService.listResourceTypes() + val resourceTypes = rbacCommonService.listResourceTypes() .map { it.resourceType } val traceId = MDC.get(TraceTag.BIZID) val compareFuture = resourceTypes.map { resourceType -> diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt index 7e286071d369..b7846c0d8dcd 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV0PolicyService.kt @@ -29,7 +29,6 @@ package com.tencent.devops.auth.provider.rbac.service.migrate import com.tencent.bk.sdk.iam.config.IamConfiguration -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.bk.sdk.iam.dto.V2PageInfoDTO import com.tencent.bk.sdk.iam.dto.manager.Action import com.tencent.bk.sdk.iam.dto.manager.AuthorizationScopes @@ -41,9 +40,10 @@ import com.tencent.bk.sdk.iam.service.v2.V2ManagerService import com.tencent.devops.auth.dao.AuthMigrationDao import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao +import com.tencent.devops.auth.pojo.enum.MemberType import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.provider.rbac.service.AuthResourceCodeConverter -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.service.DeptService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService @@ -68,7 +68,7 @@ class MigrateV0PolicyService constructor( private val migrateIamApiService: MigrateIamApiService, private val authResourceCodeConverter: AuthResourceCodeConverter, private val permissionService: PermissionService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val authMigrationDao: AuthMigrationDao, private val deptService: DeptService, private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, @@ -82,7 +82,7 @@ class MigrateV0PolicyService constructor( migrateIamApiService = migrateIamApiService, authMigrationDao = authMigrationDao, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, permissionResourceMemberService = permissionResourceMemberService @@ -486,7 +486,7 @@ class MigrateV0PolicyService constructor( groupId = it.toInt(), defaultGroup = true, member = RoleGroupMemberInfo().apply { - type = ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE) + type = MemberType.TEMPLATE.type id = subjectTemplateId name = subjectTemplateId expiredAt = 0 diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV3PolicyService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV3PolicyService.kt index fb8abd5d7885..40900d0a9a35 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV3PolicyService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/MigrateV3PolicyService.kt @@ -41,7 +41,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.provider.rbac.service.AuthResourceCodeConverter -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.service.DeptService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService @@ -72,7 +72,7 @@ class MigrateV3PolicyService constructor( private val migrateIamApiService: MigrateIamApiService, private val authResourceCodeConverter: AuthResourceCodeConverter, private val permissionService: PermissionService, - private val rbacCacheService: RbacCacheService, + private val rbacCommonService: RbacCommonService, private val authMigrationDao: AuthMigrationDao, private val deptService: DeptService, private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, @@ -86,7 +86,7 @@ class MigrateV3PolicyService constructor( migrateIamApiService = migrateIamApiService, authMigrationDao = authMigrationDao, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, permissionResourceMemberService = permissionResourceMemberService diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt index 6c6f54198f84..669140ae1b5c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt @@ -37,7 +37,6 @@ import com.tencent.devops.auth.pojo.dto.PermissionHandoverDTO import com.tencent.devops.auth.pojo.enum.AuthMigrateStatus import com.tencent.devops.auth.provider.rbac.service.AuthResourceService import com.tencent.devops.auth.provider.rbac.service.PermissionGradeManagerService -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService import com.tencent.devops.auth.service.iam.MigrateCreatorFixService import com.tencent.devops.auth.service.iam.PermissionMigrateService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService @@ -68,7 +67,7 @@ import java.util.concurrent.Executors * rbac迁移服务 */ @Suppress("LongParameterList", "ReturnCount") -class RbacPermissionMigrateService constructor( +class RbacPermissionMigrateService( private val client: Client, private val migrateResourceService: MigrateResourceService, private val migrateV3PolicyService: MigrateV3PolicyService, @@ -82,7 +81,6 @@ class RbacPermissionMigrateService constructor( private val dslContext: DSLContext, private val authMigrationDao: AuthMigrationDao, private val authMonitorSpaceDao: AuthMonitorSpaceDao, - private val cacheService: RbacCacheService, private val permissionResourceMemberService: PermissionResourceMemberService, private val migrateResourceAuthorizationService: MigrateResourceAuthorizationService, private val migrateResourceGroupService: MigrateResourceGroupService diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt index 84587ca37b79..d7bc98262d44 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/config/MockAuthConfiguration.kt @@ -8,9 +8,10 @@ import com.tencent.devops.auth.provider.sample.service.SampleAuthPermissionServi import com.tencent.devops.auth.provider.sample.service.SampleOrganizationService import com.tencent.devops.auth.provider.sample.service.SamplePermissionApplyService import com.tencent.devops.auth.provider.sample.service.SamplePermissionExtService +import com.tencent.devops.auth.provider.sample.service.SamplePermissionHandoverApplicationService import com.tencent.devops.auth.provider.sample.service.SamplePermissionItsmCallbackService import com.tencent.devops.auth.provider.sample.service.SamplePermissionMigrateService -import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.provider.sample.service.SamplePermissionManageFacadeService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupPermissionService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupService import com.tencent.devops.auth.provider.sample.service.SamplePermissionResourceGroupSyncService @@ -27,10 +28,11 @@ import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.SuperManagerService import com.tencent.devops.auth.service.iam.PermissionApplyService import com.tencent.devops.auth.service.iam.PermissionExtService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService import com.tencent.devops.auth.service.iam.PermissionItsmCallbackService import com.tencent.devops.auth.service.iam.PermissionMigrateService import com.tencent.devops.auth.service.iam.PermissionProjectService -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceGroupSyncService @@ -86,8 +88,8 @@ class MockAuthConfiguration { fun samplePermissionResourceGroupService() = SamplePermissionResourceGroupService() @Bean - @ConditionalOnMissingBean(PermissionResourceGroupAndMemberFacadeService::class) - fun samplePermissionResourceGroupAndMemberFacadeService() = SamplePermissionResourceGroupAndMemberFacadeService() + @ConditionalOnMissingBean(PermissionManageFacadeService::class) + fun samplePermissionManageFacadeService() = SamplePermissionManageFacadeService() @Bean @ConditionalOnMissingBean(PermissionResourceGroupPermissionService::class) @@ -128,4 +130,8 @@ class MockAuthConfiguration { @Bean @ConditionalOnMissingBean(PermissionResourceGroupSyncService::class) fun samplePermissionResourceGroupSyncService() = SamplePermissionResourceGroupSyncService() + + @Bean + @ConditionalOnMissingBean(PermissionHandoverApplicationService::class) + fun samplePermissionHandoverService() = SamplePermissionHandoverApplicationService() } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthMonitorSpaceService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthMonitorSpaceService.kt index 7c61b1041865..3a00e83ab7cc 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthMonitorSpaceService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthMonitorSpaceService.kt @@ -16,6 +16,10 @@ class SampleAuthMonitorSpaceService : AuthMonitorSpaceService { override fun getMonitorSpaceBizId(projectCode: String): String = "" + override fun listMonitorSpaceBizIds(projectCode: List): Map { + return emptyMap() + } + override fun getMonitorSpaceDetail(spaceUid: String): MonitorSpaceDetailVO? = null override fun getMonitorGroupConfig(groupCode: String): String? = null diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthPermissionProjectService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthPermissionProjectService.kt index 121f083af8d1..11848dede3c3 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthPermissionProjectService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SampleAuthPermissionProjectService.kt @@ -31,6 +31,10 @@ class SampleAuthPermissionProjectService : PermissionProjectService { return true } + override fun isProjectMember(userId: String, projectCode: String): Boolean { + return true + } + override fun checkUserInProjectLevelGroup(userId: String, projectCode: String): Boolean { return false } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt new file mode 100644 index 000000000000..b0be465262be --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionHandoverApplicationService.kt @@ -0,0 +1,69 @@ +package com.tencent.devops.auth.provider.sample.service + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.common.api.model.SQLPage + +class SamplePermissionHandoverApplicationService : PermissionHandoverApplicationService { + override fun createHandoverApplication( + overview: HandoverOverviewCreateDTO, + details: List + ): String { + return "" + } + + override fun generateFlowNo(): String = "" + + override fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) { + return + } + + override fun getHandoverOverview(flowNo: String): HandoverOverviewVo { + TODO("Not yet implemented") + } + + override fun listHandoverOverviews(queryRequest: HandoverOverviewQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listAuthorizationsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listGroupsOfHandoverApplication(queryReq: HandoverDetailsQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun getResourceType2CountOfHandoverApplication(flowNo: String): List { + return emptyList() + } + + override fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String?, + handoverType: HandoverType? + ): List { + return emptyList() + } + + override fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? + ): List { + return emptyList() + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt new file mode 100644 index 000000000000..f0a56157cf85 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionManageFacadeService.kt @@ -0,0 +1,177 @@ +package com.tencent.devops.auth.provider.sample.service + +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewBatchUpdateReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.common.api.model.SQLPage + +class SamplePermissionManageFacadeService : PermissionManageFacadeService { + override fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel?, + start: Int?, + limit: Int? + ): SQLPage = SQLPage(0, emptyList()) + + override fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? + ): List = emptyList() + + override fun listIamGroupIdsByConditions( + condition: IamGroupIdsQueryConditionDTO + ): List = emptyList() + + override fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List = emptyList() + + override fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String?, + iamGroupIds: List?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + operateChannel: OperateChannel?, + filterMemberType: MemberType?, + excludeIamGroupIds: List?, + onlyExcludeUserDirectlyJoined: Boolean?, + start: Int?, + limit: Int? + ): Pair> = Pair(0, emptyList()) + + override fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage = SQLPage(0, emptyList()) + + override fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIdsOfDirectlyJoined: List, + memberId: String + ): InvalidAuthorizationsDTO = InvalidAuthorizationsDTO(emptyList(), emptyList()) + + override fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean = true + + override fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean = true + + override fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean = true + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): String = "" + + override fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean = true + + override fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): String = "true" + + override fun deleteResourceGroupMembers( + userId: String, + projectCode: String, + groupId: Int, + targetMember: ResourceMemberInfo + ): Boolean = true + + override fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo = BatchOperateGroupMemberCheckVo(totalCount = 0) + + override fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List = emptyList() + + override fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean = true + + override fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean = true + + override fun batchHandleHanoverApplications(request: HandoverOverviewBatchUpdateReq): Boolean = true + + override fun getResourceType2CountOfHandover( + queryReq: ResourceType2CountOfHandoverQuery + ): List { + return emptyList() + } + + override fun listAuthorizationsOfHandover( + queryReq: HandoverDetailsQueryReq + ): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage { + return SQLPage(0, emptyList()) + } + + override fun isProjectMember(projectCode: String, userId: String): Boolean { + return true + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt deleted file mode 100644 index 289455d58561..000000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupAndMemberFacadeService.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tencent.devops.auth.provider.sample.service - -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService -import com.tencent.devops.common.api.model.SQLPage - -class SamplePermissionResourceGroupAndMemberFacadeService : PermissionResourceGroupAndMemberFacadeService { - override fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String?, - start: Int?, - limit: Int? - ): SQLPage = SQLPage(0, emptyList()) - - override fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List = emptyList() - - override fun listIamGroupIdsByConditions( - condition: IamGroupIdsQueryConditionDTO - ): List = emptyList() - - override fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage = SQLPage(0, emptyList()) -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt index 1688b7133dca..997ad7c9ab6b 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceGroupPermissionService.kt @@ -86,6 +86,12 @@ class SamplePermissionResourceGroupPermissionService : PermissionResourceGroupPe action: String ): Boolean = true + override fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean = true + override fun listGroupResourcesWithPermission( projectCode: String, filterIamGroupIds: List, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt index c412788f26e0..79d4ada40a7a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt @@ -1,16 +1,8 @@ package com.tencent.devops.auth.provider.sample.service import com.tencent.bk.sdk.iam.dto.manager.ManagerMember -import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.model.SQLPage @@ -70,64 +62,18 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { memberRenewalDTO: GroupMemberRenewalDTO ): Boolean = true - override fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean = true - override fun renewalIamGroupMembers( groupId: Int, members: List, expiredAt: Long ): Boolean = true - override fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean = true - - override fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean = true - override fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean = true - override fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean = true - - override fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo = BatchOperateGroupMemberCheckVo( - totalCount = 0, - inoperableCount = 0 - ) - - override fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List = emptyList() - - override fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean = true - override fun addGroupMember( projectCode: String, memberId: String, @@ -136,9 +82,11 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { iamGroupId: Int ): Boolean = true - override fun addIamGroupMember(groupId: Int, members: List, expiredAt: Long): Boolean { - TODO("Not yet implemented") - } + override fun addIamGroupMember( + groupId: Int, + members: List, + expiredAt: Long + ): Boolean = true override fun getProjectMemberCount(projectCode: String): ResourceMemberCountVO = ResourceMemberCountVO( @@ -161,20 +109,4 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { override fun addDepartedFlagToMembers( records: List ): List = emptyList() - - override fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String?, - iamGroupIds: List?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - start: Int?, - limit: Int? - ): Pair> = Pair(0, emptyList()) - - override fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List = emptyList() } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt index b2ba564a4cfa..d8b5f1f1e076 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceValidateService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.provider.sample.service import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.service.iam.PermissionResourceValidateService class SamplePermissionResourceValidateService : PermissionResourceValidateService { @@ -46,4 +47,13 @@ class SamplePermissionResourceValidateService : PermissionResourceValidateServic resourceType: String, resourceCode: String ): Boolean = true + + override fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) { + return + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/stream/service/StreamPermissionProjectServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/stream/service/StreamPermissionProjectServiceImpl.kt index 696894dcd421..998288373c0b 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/stream/service/StreamPermissionProjectServiceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/stream/service/StreamPermissionProjectServiceImpl.kt @@ -67,6 +67,10 @@ class StreamPermissionProjectServiceImpl @Autowired constructor( return streamPermissionService.isProjectMember(projectCode, userId).first } + override fun isProjectMember(userId: String, projectCode: String): Boolean { + return streamPermissionService.isProjectMember(projectCode, userId).first + } + override fun checkUserInProjectLevelGroup(userId: String, projectCode: String): Boolean { return true } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceMonitorSpaceResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceMonitorSpaceResourceImpl.kt index 763b09e70b29..24ecea32600b 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceMonitorSpaceResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceMonitorSpaceResourceImpl.kt @@ -43,6 +43,10 @@ class ServiceMonitorSpaceResourceImpl @Autowired constructor( return Result(monitorSpaceService.getMonitorSpaceBizId(projectCode)) } + override fun listMonitorSpaceBizIds(projectCodes: List): Result> { + return Result(monitorSpaceService.listMonitorSpaceBizIds(projectCodes)) + } + override fun migrateMonitorResource( projectCodes: List, asyncMigrateOtherGroup: Boolean? diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt index e94939ddc5b3..8aca9e3d1384 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceProjectAuthResourceImpl.kt @@ -28,7 +28,9 @@ package com.tencent.devops.auth.resources.service import com.tencent.devops.auth.api.service.ServiceProjectAuthResource +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ProjectPermissionInfoVO +import com.tencent.devops.auth.service.PermissionAuthorizationService import com.tencent.devops.auth.service.iam.PermissionProjectService import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.pojo.BKAuthProjectRolesResources @@ -41,7 +43,8 @@ import org.springframework.beans.factory.annotation.Autowired @RestResource class ServiceProjectAuthResourceImpl @Autowired constructor( - val permissionProjectService: PermissionProjectService + val permissionProjectService: PermissionProjectService, + val permissionAuthorizationService: PermissionAuthorizationService ) : ServiceProjectAuthResource { @BkApiPermission([BkApiHandleType.API_OPEN_TOKEN_CHECK]) override fun getProjectUsers( @@ -106,6 +109,18 @@ class ServiceProjectAuthResourceImpl @Autowired constructor( ) } + override fun isProjectMember( + userId: String, + projectCode: String + ): Result { + return Result( + permissionProjectService.isProjectMember( + userId = userId, + projectCode = projectCode + ) + ) + } + @BkApiPermission([BkApiHandleType.API_OPEN_TOKEN_CHECK]) override fun checkUserInProjectLevelGroup( token: String, @@ -201,4 +216,12 @@ class ServiceProjectAuthResourceImpl @Autowired constructor( ) ) } + + override fun listUserProjectsWithAuthorization(userId: String): Result> { + return Result( + permissionAuthorizationService.listUserProjectsWithAuthorization( + userId = userId + ) + ) + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt index cea4a8350a87..52fde466e357 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceGroupResourceImpl.kt @@ -5,7 +5,7 @@ import com.tencent.devops.auth.pojo.dto.GroupAddDTO import com.tencent.devops.auth.pojo.request.CustomGroupCreateReq import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.GroupPermissionDetailVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.common.api.model.SQLPage @@ -17,7 +17,7 @@ import com.tencent.devops.common.web.RestResource class ServiceResourceGroupResourceImpl( val permissionResourceGroupService: PermissionResourceGroupService, val resourceGroupPermissionService: PermissionResourceGroupPermissionService, - val resourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + val permissionManageFacadeService: PermissionManageFacadeService ) : ServiceResourceGroupResource { override fun getGroupPermissionDetail( projectCode: String, @@ -45,7 +45,7 @@ class ServiceResourceGroupResourceImpl( limit: Int? ): Result> { return Result( - resourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectCode, resourceType = resourceType, memberId = memberId, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt index 02c0636974ff..aadd867c2b1d 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/service/ServiceResourceMemberResourceImpl.kt @@ -2,6 +2,7 @@ package com.tencent.devops.auth.resources.service import com.tencent.devops.auth.api.service.ServiceResourceMemberResource import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.pojo.BkAuthGroup @@ -14,8 +15,9 @@ import com.tencent.devops.project.pojo.ProjectDeleteUserInfo import java.util.concurrent.TimeUnit @RestResource -class ServiceResourceMemberResourceImpl constructor( - private val permissionResourceMemberService: PermissionResourceMemberService +class ServiceResourceMemberResourceImpl( + private val permissionResourceMemberService: PermissionResourceMemberService, + private val permissionManageFacadeService: PermissionManageFacadeService ) : ServiceResourceMemberResource { @BkApiPermission([BkApiHandleType.API_OPEN_TOKEN_CHECK]) override fun getResourceGroupMembers( @@ -107,7 +109,7 @@ class ServiceResourceMemberResourceImpl constructor( renewalConditionReq: GroupMemberSingleRenewalReq ): Result { return Result( - permissionResourceMemberService.renewalGroupMember( + permissionManageFacadeService.renewalGroupMember( userId = userId, projectCode = projectCode, renewalConditionReq = renewalConditionReq diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt index e4c5ed68aa46..68e72114d5b7 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthAuthorizationResourceImpl.kt @@ -1,8 +1,11 @@ package com.tencent.devops.auth.resources.user import com.tencent.devops.auth.api.user.UserAuthAuthorizationResource +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.BkManagerCheck @@ -16,17 +19,25 @@ import com.tencent.devops.common.web.RestResource @RestResource class UserAuthAuthorizationResourceImpl( - val permissionAuthorizationService: PermissionAuthorizationService + val permissionAuthorizationService: PermissionAuthorizationService, + val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthAuthorizationResource { - @BkManagerCheck override fun listResourceAuthorization( userId: String, projectId: String, + operateChannel: OperateChannel?, condition: ResourceAuthorizationConditionRequest ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = if (operateChannel == OperateChannel.PERSONAL) condition.handoverFrom!! else userId + ) return Result( permissionAuthorizationService.listResourceAuthorizations( - condition = condition + condition = condition, + operateChannel = operateChannel ) ) } @@ -93,4 +104,8 @@ class UserAuthAuthorizationResourceImpl( ) ) } + + override fun listUserProjectsWithAuthorization(userId: String): Result> { + return Result(permissionAuthorizationService.listUserProjectsWithAuthorization(userId)) + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt new file mode 100644 index 000000000000..10c8f060250d --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthHandoverResourceImpl.kt @@ -0,0 +1,101 @@ +package com.tencent.devops.auth.resources.user + +import com.tencent.devops.auth.api.user.UserAuthHandoverResource +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewBatchUpdateReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.PermissionAuthorizationService +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService +import com.tencent.devops.common.api.exception.PermissionForbiddenException +import com.tencent.devops.common.api.model.SQLPage +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationHandoverConditionRequest +import com.tencent.devops.common.web.RestResource + +@RestResource +class UserAuthHandoverResourceImpl( + private val permissionAuthorizationService: PermissionAuthorizationService, + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService, + private val permissionResourceValidateService: PermissionResourceValidateService +) : UserAuthHandoverResource { + override fun handoverAuthorizationsApplication( + userId: String, + projectId: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = condition.handoverFrom!! + ) + return Result( + permissionAuthorizationService.handoverAuthorizationsApplication( + operator = userId, + projectCode = projectId, + condition = condition + ) + ) + } + + override fun listHandoverOverviews( + userId: String, + queryRequest: HandoverOverviewQueryReq + ): Result> { + if (userId != queryRequest.memberId) { + throw PermissionForbiddenException( + message = "You have not permission to view other people's handover details!" + ) + } + + return Result(permissionHandoverApplicationService.listHandoverOverviews(queryRequest = queryRequest)) + } + + override fun getResourceType2CountOfHandover( + userId: String, + queryReq: ResourceType2CountOfHandoverQuery + ): Result> { + return Result(permissionManageFacadeService.getResourceType2CountOfHandover(queryReq = queryReq)) + } + + override fun listAuthorizationsOfHandover( + userId: String, + queryReq: HandoverDetailsQueryReq + ): Result> { + return Result(permissionManageFacadeService.listAuthorizationsOfHandover(queryReq = queryReq)) + } + + override fun listGroupsOfHandover( + userId: String, + queryReq: HandoverDetailsQueryReq + ): Result> { + return Result(permissionManageFacadeService.listGroupsOfHandover(queryReq = queryReq)) + } + + override fun handleHanoverApplication(userId: String, request: HandoverOverviewUpdateReq): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = request.projectCode, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = request.operator + ) + return Result(permissionManageFacadeService.handleHanoverApplication(request = request)) + } + + override fun batchHandleHanoverApplications( + userId: String, + request: HandoverOverviewBatchUpdateReq + ): Result { + return Result(permissionManageFacadeService.batchHandleHanoverApplications(request = request)) + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt index 4c99458fb86f..4a07c5e9e6ff 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceGroupResourceImpl.kt @@ -28,18 +28,22 @@ package com.tencent.devops.auth.resources.user -import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.api.user.UserAuthResourceGroupResource import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO +import com.tencent.devops.auth.pojo.dto.MemberGroupJoinedDTO import com.tencent.devops.auth.pojo.dto.RenameGroupDTO -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.enum.JoinedType +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo import com.tencent.devops.auth.pojo.vo.IamGroupPoliciesVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceGroupPermissionService import com.tencent.devops.auth.service.iam.PermissionResourceGroupService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.auth.api.BkManagerCheck @@ -50,8 +54,9 @@ import org.springframework.beans.factory.annotation.Autowired class UserAuthResourceGroupResourceImpl @Autowired constructor( private val permissionResourceGroupService: PermissionResourceGroupService, private val permissionResourceMemberService: PermissionResourceMemberService, - private val permissionResourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService, - private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionResourceGroupPermissionService: PermissionResourceGroupPermissionService, + private val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthResourceGroupResource { override fun getGroupPolicies( userId: String, @@ -69,7 +74,6 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( ) } - @BkManagerCheck override fun getMemberGroupsDetails( userId: String, projectId: String, @@ -81,11 +85,19 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( relatedResourceType: String?, relatedResourceCode: String?, action: String?, + operateChannel: OperateChannel?, start: Int, limit: Int ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = memberId + ) + return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectId, resourceType = resourceType, memberId = memberId, @@ -94,6 +106,7 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( maxExpiredAt = maxExpiredAt, relatedResourceType = relatedResourceType, relatedResourceCode = relatedResourceCode, + operateChannel = operateChannel, action = action, start = start, limit = limit @@ -101,6 +114,30 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( ) } + override fun getMemberGroupDetails( + userId: String, + projectId: String, + resourceType: String, + groupId: Int, + memberId: String + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = memberId + ) + return Result( + permissionManageFacadeService.getMemberGroupsDetails( + projectId = projectId, + memberId = memberId, + resourceType = resourceType, + iamGroupIds = listOf(groupId), + operateChannel = OperateChannel.PERSONAL + ).records.first { it.groupId == groupId && it.joinedType == JoinedType.DIRECT } + ) + } + override fun renewal( userId: String, projectId: String, @@ -108,6 +145,12 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( groupId: Int, memberRenewalDTO: GroupMemberRenewalDTO ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = userId + ) return Result( permissionResourceMemberService.renewalGroupMember( userId = userId, @@ -126,14 +169,19 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( groupId: Int ): Result { return Result( - permissionResourceMemberService.batchDeleteResourceGroupMembers( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromManager( userId = userId, projectCode = projectId, - removeMemberDTO = GroupMemberCommonConditionReq( - groupIds = listOf(groupId), + removeMemberDTO = GroupMemberRemoveConditionReq( + groupIds = listOf( + MemberGroupJoinedDTO( + id = groupId, + memberType = MemberType.USER + ) + ), targetMember = ResourceMemberInfo( id = userId, - type = ManagerScopesEnum.getType(ManagerScopesEnum.USER) + type = MemberType.USER.type ) ) ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt index bd76fea6919b..617e22a6197a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/user/UserAuthResourceMemberResourceImpl.kt @@ -3,17 +3,20 @@ package com.tencent.devops.auth.resources.user import com.tencent.devops.auth.api.user.UserAuthResourceMemberResource import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.OperateChannel import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.auth.service.iam.PermissionResourceGroupAndMemberFacadeService +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.auth.service.iam.PermissionManageFacadeService import com.tencent.devops.auth.service.iam.PermissionResourceMemberService +import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.api.pojo.Result @@ -27,7 +30,8 @@ import com.tencent.devops.common.web.RestResource class UserAuthResourceMemberResourceImpl( private val permissionResourceMemberService: PermissionResourceMemberService, private val permissionService: PermissionService, - private val permissionResourceGroupAndMemberFacadeService: PermissionResourceGroupAndMemberFacadeService + private val permissionManageFacadeService: PermissionManageFacadeService, + private val permissionResourceValidateService: PermissionResourceValidateService ) : UserAuthResourceMemberResource { override fun listProjectMembers( userId: String, @@ -69,7 +73,7 @@ class UserAuthResourceMemberResourceImpl( projectMembersQueryConditionReq: ProjectMembersQueryConditionReq ): Result> { return Result( - permissionResourceGroupAndMemberFacadeService.listProjectMembersByComplexConditions( + permissionManageFacadeService.listProjectMembersByComplexConditions( conditionReq = projectMembersQueryConditionReq ) ) @@ -81,13 +85,13 @@ class UserAuthResourceMemberResourceImpl( projectId: String, renewalConditionReq: GroupMemberSingleRenewalReq ): Result { - permissionResourceMemberService.renewalGroupMember( + permissionManageFacadeService.renewalGroupMember( userId = userId, projectCode = projectId, renewalConditionReq = renewalConditionReq ) return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsDetails( + permissionManageFacadeService.getMemberGroupsDetails( projectId = projectId, memberId = renewalConditionReq.targetMember.id, iamGroupIds = listOf(renewalConditionReq.groupId) @@ -96,13 +100,13 @@ class UserAuthResourceMemberResourceImpl( } @BkManagerCheck - override fun batchRenewalGroupMembers( + override fun batchRenewalGroupMembersFromManager( userId: String, projectId: String, renewalConditionReq: GroupMemberRenewalConditionReq ): Result { return Result( - permissionResourceMemberService.batchRenewalGroupMembers( + permissionManageFacadeService.batchRenewalGroupMembersFromManager( userId = userId, projectCode = projectId, renewalConditionReq = renewalConditionReq @@ -111,13 +115,13 @@ class UserAuthResourceMemberResourceImpl( } @BkManagerCheck - override fun batchRemoveGroupMembers( + override fun batchRemoveGroupMembersFromManager( userId: String, projectId: String, - removeMemberDTO: GroupMemberCommonConditionReq + removeMemberDTO: GroupMemberRemoveConditionReq ): Result { return Result( - permissionResourceMemberService.batchDeleteResourceGroupMembers( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromManager( userId = userId, projectCode = projectId, removeMemberDTO = removeMemberDTO @@ -125,14 +129,77 @@ class UserAuthResourceMemberResourceImpl( ) } + override fun batchRemoveGroupMembersFromPersonal( + userId: String, + projectId: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = removeMemberDTO.targetMember.id + ) + return Result( + permissionManageFacadeService.batchDeleteResourceGroupMembersFromPersonal( + userId = userId, + projectCode = projectId, + removeMemberDTO = removeMemberDTO + ) + ) + } + + override fun deleteResourceGroupMembers( + userId: String, + projectId: String, + groupId: Int, + operateChannel: OperateChannel, + targetMember: ResourceMemberInfo + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel, + targetMemberId = targetMember.id + ) + return Result( + permissionManageFacadeService.deleteResourceGroupMembers( + userId = userId, + projectCode = projectId, + groupId = groupId, + targetMember = targetMember + ) + ) + } + @BkManagerCheck - override fun batchHandoverGroupMembers( + override fun batchHandoverGroupMembersFromManager( userId: String, projectId: String, handoverMemberDTO: GroupMemberHandoverConditionReq ): Result { return Result( - permissionResourceMemberService.batchHandoverGroupMembers( + permissionManageFacadeService.batchHandoverGroupMembersFromManager( + userId = userId, + projectCode = projectId, + handoverMemberDTO = handoverMemberDTO + ) + ) + } + + override fun batchHandoverApplicationFromPersonal( + userId: String, + projectId: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = OperateChannel.PERSONAL, + targetMemberId = handoverMemberDTO.targetMember.id + ) + return Result( + permissionManageFacadeService.batchHandoverApplicationFromPersonal( userId = userId, projectCode = projectId, handoverMemberDTO = handoverMemberDTO @@ -140,15 +207,20 @@ class UserAuthResourceMemberResourceImpl( ) } - @BkManagerCheck override fun batchOperateGroupMembersCheck( userId: String, projectId: String, batchOperateType: BatchOperateType, conditionReq: GroupMemberCommonConditionReq ): Result { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = conditionReq.operateChannel, + targetMemberId = conditionReq.targetMember.id + ) return Result( - permissionResourceMemberService.batchOperateGroupMembersCheck( + permissionManageFacadeService.batchOperateGroupMembersCheck( userId = userId, projectCode = projectId, batchOperateType = batchOperateType, @@ -164,7 +236,7 @@ class UserAuthResourceMemberResourceImpl( removeMemberFromProjectReq: RemoveMemberFromProjectReq ): Result> { return Result( - permissionResourceMemberService.removeMemberFromProject( + permissionManageFacadeService.removeMemberFromProject( userId = userId, projectCode = projectId, removeMemberFromProjectReq = removeMemberFromProjectReq @@ -179,7 +251,7 @@ class UserAuthResourceMemberResourceImpl( removeMemberFromProjectReq: RemoveMemberFromProjectReq ): Result { return Result( - permissionResourceMemberService.removeMemberFromProjectCheck( + permissionManageFacadeService.removeMemberFromProjectCheck( userId = userId, projectCode = projectId, removeMemberFromProjectReq = removeMemberFromProjectReq @@ -187,7 +259,6 @@ class UserAuthResourceMemberResourceImpl( ) } - @BkManagerCheck override fun getMemberGroupCount( userId: String, projectId: String, @@ -197,10 +268,17 @@ class UserAuthResourceMemberResourceImpl( maxExpiredAt: Long?, relatedResourceType: String?, relatedResourceCode: String?, - action: String? - ): Result> { + action: String?, + operateChannel: OperateChannel? + ): Result> { + permissionResourceValidateService.validateUserProjectPermissionByChannel( + userId = userId, + projectCode = projectId, + operateChannel = operateChannel ?: OperateChannel.MANAGER, + targetMemberId = memberId + ) return Result( - permissionResourceGroupAndMemberFacadeService.getMemberGroupsCount( + permissionManageFacadeService.getMemberGroupsCount( projectCode = projectId, memberId = memberId, groupName = groupName, @@ -208,7 +286,8 @@ class UserAuthResourceMemberResourceImpl( maxExpiredAt = maxExpiredAt, relatedResourceType = relatedResourceType, relatedResourceCode = relatedResourceCode, - action = action + action = action, + operateChannel = operateChannel ) ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthMonitorSpaceService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthMonitorSpaceService.kt index 274848ddab7a..cab64256ea7a 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthMonitorSpaceService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthMonitorSpaceService.kt @@ -27,6 +27,11 @@ interface AuthMonitorSpaceService { */ fun getMonitorSpaceBizId(projectCode: String): String + /** + * 获取监控空间业务id列表 + * */ + fun listMonitorSpaceBizIds(projectCode: List): Map + /** * 获取监控空间详情 */ diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt index 301dd66bea84..6e1693899808 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationService.kt @@ -27,6 +27,8 @@ package com.tencent.devops.auth.service +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq @@ -77,9 +79,17 @@ interface PermissionAuthorizationService { * 获取项目资源授予记录--根据条件 */ fun listResourceAuthorizations( - condition: ResourceAuthorizationConditionRequest + condition: ResourceAuthorizationConditionRequest, + operateChannel: OperateChannel? = OperateChannel.MANAGER ): SQLPage + /** + * 获取用户授权相关项目 + */ + fun listUserProjectsWithAuthorization( + userId: String + ): List + /** * 修改资源授权管理 */ @@ -87,6 +97,14 @@ interface PermissionAuthorizationService { resourceAuthorizationList: List ): Boolean + /** + * 是否用户拥有项目下授权 + */ + fun isUserHasProjectAuthorizations( + projectCode: String, + userId: String + ): Boolean + /** * 删除资源授权管理 */ @@ -121,6 +139,15 @@ interface PermissionAuthorizationService { condition: ResourceAuthorizationHandoverConditionRequest ): Map> + /** + * 交接授权申请 + */ + fun handoverAuthorizationsApplication( + operator: String, + projectCode: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): String + /** * 批量重置授权人--项目下全量 */ @@ -129,4 +156,15 @@ interface PermissionAuthorizationService { projectCode: String, condition: ResetAllResourceAuthorizationReq ): List + + /** + * 检查交接人是否有代码库授权权限 + */ + fun checkRepertoryAuthorizationsHanover( + operator: String, + projectCode: String, + repertoryIds: List, + handoverFrom: String, + handoverTo: String + ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt index dac016a70d28..341f4b1fddc7 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/PermissionAuthorizationServiceImpl.kt @@ -3,14 +3,24 @@ package com.tencent.devops.auth.service import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.constant.AuthI18nConstants import com.tencent.devops.auth.constant.AuthMessageCode +import com.tencent.devops.auth.constant.AuthMessageCode.ERROR_REPERTORY_HANDOVER_AUTHORIZATION import com.tencent.devops.auth.dao.AuthAuthorizationDao +import com.tencent.devops.auth.dao.AuthResourceDao +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverStatus +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.vo.AuthProjectVO import com.tencent.devops.auth.pojo.vo.ResourceTypeInfoVo +import com.tencent.devops.auth.service.iam.PermissionHandoverApplicationService import com.tencent.devops.auth.service.iam.PermissionResourceValidateService import com.tencent.devops.auth.service.iam.PermissionService import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.auth.api.AuthResourceType +import com.tencent.devops.common.auth.api.ResourceTypeId import com.tencent.devops.common.auth.api.pojo.ResetAllResourceAuthorizationReq import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationConditionRequest import com.tencent.devops.common.auth.api.pojo.ResourceAuthorizationDTO @@ -24,19 +34,22 @@ import com.tencent.devops.common.client.Client import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.environment.api.ServiceEnvNodeAuthorizationResource import com.tencent.devops.process.api.service.ServicePipelineAuthorizationResource +import com.tencent.devops.project.api.service.ServiceProjectResource import com.tencent.devops.repository.api.ServiceRepositoryAuthorizationResource import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service -class PermissionAuthorizationServiceImpl constructor( +class PermissionAuthorizationServiceImpl( private val dslContext: DSLContext, private val authAuthorizationDao: AuthAuthorizationDao, private val client: Client, private val permissionResourceValidateService: PermissionResourceValidateService, private val deptService: DeptService, - private val permissionService: PermissionService + private val permissionService: PermissionService, + private val permissionHandoverApplicationService: PermissionHandoverApplicationService, + private val authResourceDao: AuthResourceDao ) : PermissionAuthorizationService { companion object { private val logger = LoggerFactory.getLogger(PermissionAuthorizationServiceImpl::class.java) @@ -120,22 +133,72 @@ class PermissionAuthorizationServiceImpl constructor( return true } + @Suppress("NestedBlockDepth") override fun listResourceAuthorizations( - condition: ResourceAuthorizationConditionRequest + condition: ResourceAuthorizationConditionRequest, + operateChannel: OperateChannel? ): SQLPage { - logger.info("list resource authorizations:$condition") - val record = authAuthorizationDao.list( - dslContext = dslContext, - condition = condition - ) - val count = authAuthorizationDao.count( + logger.info("list resource authorizations:$condition|$operateChannel") + val (records, count) = if (operateChannel != OperateChannel.PERSONAL) { + val records = authAuthorizationDao.list( + dslContext = dslContext, + condition = condition + ) + val count = authAuthorizationDao.count( + dslContext = dslContext, + condition = condition + ) + Pair(records, count) + } else { + val beingHandoverDetails = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = condition.projectCode, + memberId = condition.handoverFrom!!, + handoverType = HandoverType.AUTHORIZATION, + resourceType = condition.resourceType!! + ) + val beingHandoverResourceCodes = beingHandoverDetails.map { it.itemId }.distinct() + if (condition.queryHandover == true && beingHandoverResourceCodes.isEmpty()) { + Pair(emptyList(), 0L) + } else { + val finalCondition = condition.apply { + when (this.queryHandover) { + true -> this.filterResourceCodes = beingHandoverResourceCodes + false -> this.excludeResourceCodes = beingHandoverResourceCodes + else -> {} + } + } + val records = authAuthorizationDao.list( + dslContext = dslContext, + condition = finalCondition + ).map { + it.copy( + beingHandover = beingHandoverResourceCodes.contains(it.resourceCode), + approver = beingHandoverDetails.find { details -> details.itemId == it.resourceCode }?.approver + ) + } + val count = authAuthorizationDao.count( + dslContext = dslContext, + condition = finalCondition + ) + Pair(records, count) + } + } + return SQLPage(count = count.toLong(), records = records) + } + + override fun listUserProjectsWithAuthorization(userId: String): List { + val projectCodesWithAuthorization = authAuthorizationDao.listUserProjects(dslContext, userId) + val projectInfos = authResourceDao.listByResourceCodes( dslContext = dslContext, - condition = condition - ) - return SQLPage( - count = count.toLong(), - records = record + resourceType = ResourceTypeId.PROJECT, + resourceCodes = projectCodesWithAuthorization ) + return projectInfos.map { + AuthProjectVO( + projectCode = it.resourceCode, + projectName = it.resourceName + ) + } } override fun modifyResourceAuthorization(resourceAuthorizationList: List): Boolean { @@ -148,6 +211,19 @@ class PermissionAuthorizationServiceImpl constructor( return true } + override fun isUserHasProjectAuthorizations( + projectCode: String, + userId: String + ): Boolean { + return authAuthorizationDao.count( + dslContext = dslContext, + condition = ResourceAuthorizationConditionRequest( + projectCode = projectCode, + handoverFrom = userId + ) + ) > 0 + } + override fun deleteResourceAuthorization( projectCode: String, resourceType: String, @@ -226,6 +302,60 @@ class PermissionAuthorizationServiceImpl constructor( return result } + override fun handoverAuthorizationsApplication( + operator: String, + projectCode: String, + condition: ResourceAuthorizationHandoverConditionRequest + ): String { + val beingHandoverDetails = permissionHandoverApplicationService.listMemberHandoverDetails( + projectCode = condition.projectCode, + memberId = condition.handoverFrom!!, + handoverType = HandoverType.AUTHORIZATION, + resourceType = condition.resourceType + ).map { it.itemId }.distinct() + + val finalCondition = condition.copy( + preCheck = true, + checkPermission = false, + excludeResourceCodes = beingHandoverDetails + ) + + val handoverResult = resetResourceAuthorizationByResourceType( + operator = operator, + projectCode = projectCode, + condition = finalCondition + ) + if (!handoverResult[ResourceAuthorizationHandoverStatus.FAILED].isNullOrEmpty()) { + throw ErrorCodeException(errorCode = ERROR_REPERTORY_HANDOVER_AUTHORIZATION) + } + val resourceAuthorizationList = getResourceAuthorizationList(condition = finalCondition) + val handoverDetails = mutableListOf() + resourceAuthorizationList.forEach { authorization -> + handoverDetails.add( + HandoverDetailDTO( + projectCode = projectCode, + itemId = authorization.resourceCode, + resourceType = authorization.resourceType, + handoverType = HandoverType.AUTHORIZATION + ) + ) + } + // 创建交接单 + val flowNo = permissionHandoverApplicationService.createHandoverApplication( + overview = HandoverOverviewCreateDTO( + projectCode = projectCode, + projectName = client.get(ServiceProjectResource::class).get(projectCode).data!!.projectName, + applicant = condition.handoverFrom!!, + approver = condition.handoverTo!!, + handoverStatus = HandoverStatus.PENDING, + groupCount = 0, + authorizationCount = resourceAuthorizationList.size + ), + details = handoverDetails + ) + return flowNo + } + override fun resetAllResourceAuthorization( operator: String, projectCode: String, @@ -261,6 +391,35 @@ class PermissionAuthorizationServiceImpl constructor( return result } + override fun checkRepertoryAuthorizationsHanover( + operator: String, + projectCode: String, + repertoryIds: List, + handoverFrom: String, + handoverTo: String + ) { + val canHandoverRepertory = resetResourceAuthorizationByResourceType( + operator = operator, + projectCode = projectCode, + condition = ResourceAuthorizationHandoverConditionRequest( + projectCode = projectCode, + resourceType = ResourceTypeId.REPERTORY, + fullSelection = true, + filterResourceCodes = repertoryIds, + handoverChannel = HandoverChannelCode.MANAGER, + handoverFrom = handoverFrom, + handoverTo = handoverTo, + checkPermission = false, + preCheck = true + ) + )[ResourceAuthorizationHandoverStatus.FAILED].isNullOrEmpty() + if (!canHandoverRepertory) { + throw ErrorCodeException( + errorCode = ERROR_REPERTORY_HANDOVER_AUTHORIZATION + ) + } + } + private fun addHandoverFromCnName( resourceAuthorizationList: List ) { @@ -352,6 +511,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + AuthResourceType.CODE_REPERTORY.value -> { client.get(ServiceRepositoryAuthorizationResource::class).resetRepositoryAuthorization( projectId = projectId, @@ -359,6 +519,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + AuthResourceType.ENVIRONMENT_ENV_NODE.value -> { client.get(ServiceEnvNodeAuthorizationResource::class).resetEnvNodeAuthorization( projectId = projectId, @@ -366,6 +527,7 @@ class PermissionAuthorizationServiceImpl constructor( resourceAuthorizationHandoverDTOs = resourceAuthorizationHandoverDTOs ).data } + else -> { null } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt new file mode 100644 index 000000000000..9565b9c44a1b --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionHandoverApplicationService.kt @@ -0,0 +1,80 @@ +package com.tencent.devops.auth.service.iam + +import com.tencent.devops.auth.pojo.dto.HandoverDetailDTO +import com.tencent.devops.auth.pojo.dto.HandoverOverviewCreateDTO +import com.tencent.devops.auth.pojo.enum.HandoverType +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverOverviewVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.model.SQLPage + +interface PermissionHandoverApplicationService { + /** + * 创建权限交接申请单 + * */ + fun createHandoverApplication( + overview: HandoverOverviewCreateDTO, + details: List + ): String + + /** + * 生成流程单号 + * */ + fun generateFlowNo(): String + + /** + * 更新权限交接申请单 + * */ + fun updateHandoverApplication(overview: HandoverOverviewUpdateReq) + + /** + * 根据流程单号获取权限交接总览 + * */ + fun getHandoverOverview(flowNo: String): HandoverOverviewVo + + /** + * 权限交接总览列表 + * */ + fun listHandoverOverviews(queryRequest: HandoverOverviewQueryReq): SQLPage + + /** + * 获取交接单详情 + * */ + fun listHandoverDetails( + projectCode: String, + flowNo: String, + resourceType: String? = null, + handoverType: HandoverType? = null + ): List + + /** + * 获取用户在项目下正在交接的组/授权 + * */ + fun listMemberHandoverDetails( + projectCode: String, + memberId: String, + handoverType: HandoverType, + resourceType: String? = null + ): List + + /** + * 获取交接单中授权相关 + * */ + fun listAuthorizationsOfHandoverApplication( + queryReq: HandoverDetailsQueryReq + ): SQLPage + + /** + * 获取交接单中用户组相关 + * */ + fun listGroupsOfHandoverApplication(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 根据资源类型进行分类 + * */ + fun getResourceType2CountOfHandoverApplication(flowNo: String): List +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt new file mode 100644 index 000000000000..ea693fbedf60 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionManageFacadeService.kt @@ -0,0 +1,255 @@ +package com.tencent.devops.auth.service.iam + +import com.tencent.devops.auth.pojo.AuthResourceGroupMember +import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO +import com.tencent.devops.auth.pojo.dto.InvalidAuthorizationsDTO +import com.tencent.devops.auth.pojo.enum.BatchOperateType +import com.tencent.devops.auth.pojo.enum.MemberType +import com.tencent.devops.auth.pojo.enum.OperateChannel +import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRemoveConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq +import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.HandoverDetailsQueryReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewBatchUpdateReq +import com.tencent.devops.auth.pojo.request.HandoverOverviewUpdateReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq +import com.tencent.devops.auth.pojo.request.ResourceType2CountOfHandoverQuery +import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo +import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo +import com.tencent.devops.auth.pojo.vo.HandoverAuthorizationDetailVo +import com.tencent.devops.auth.pojo.vo.HandoverGroupDetailVo +import com.tencent.devops.auth.pojo.vo.ResourceType2CountVo +import com.tencent.devops.common.api.model.SQLPage + +/** + * 权限管理门面类 + */ +interface PermissionManageFacadeService { + /** + * 查询成员所在资源用户组详情 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun getMemberGroupsDetails( + projectId: String, + memberId: String, + resourceType: String? = null, + iamGroupIds: List? = null, + groupName: String? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, + relatedResourceType: String? = null, + relatedResourceCode: String? = null, + action: String? = null, + operateChannel: OperateChannel? = OperateChannel.MANAGER, + start: Int? = null, + limit: Int? = null + ): SQLPage + + /** + * 获取用户有权限的用户组数量 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun getMemberGroupsCount( + projectCode: String, + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, + relatedResourceType: String?, + relatedResourceCode: String?, + action: String?, + operateChannel: OperateChannel? = OperateChannel.MANAGER + ): List + + /** + * 根据条件查询组ID + * */ + fun listIamGroupIdsByConditions( + condition: IamGroupIdsQueryConditionDTO + ): List + + /** + * 获取用户在该项目加入的组 + * */ + fun listMemberGroupIdsInProject( + projectCode: String, + memberId: String + ): List + + /** + * 查询成员所在资源用户组列表 + * 管理员视角返回用户直接加入/模板加入的用户组 + * 个人视角返回用户直接加入/模板加入/组织加入的用户组 + * */ + fun listResourceGroupMembers( + projectCode: String, + memberId: String, + resourceType: String? = null, + iamGroupIds: List? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, + operateChannel: OperateChannel? = OperateChannel.MANAGER, + filterMemberType: MemberType? = null, + excludeIamGroupIds: List? = null, + /*与excludeIamGroupIds参数搭配使用,用于排除用户直接加入的组*/ + onlyExcludeUserDirectlyJoined: Boolean? = false, + start: Int? = null, + limit: Int? = null + ): Pair> + + /** + * 根据复杂条件进行搜索,用于用户管理界面 + * */ + fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage + + /** + * 为了避免流授权失效,需要对用户退出/交接用户组进行检查。 + * 入参: + * 1、项目ID + * 2、用户交接/移除的用户组(直接加入) + * 3、用户ID + * 返回结果: + * 1、引起代持人权限失效的用户组。 + * 2、引起代持人权限失效的流水线。 + * 3、引起代码库oauth失效的代码库(当用户操作完组后,不再拥有项目访问权限时,会代码库oauth引起失效) + * 4、引起失效的环境节点授权 + **/ + fun listInvalidAuthorizationsAfterOperatedGroups( + projectCode: String, + iamGroupIdsOfDirectlyJoined: List, + memberId: String + ): InvalidAuthorizationsDTO + + /** + * 续期用户权限-无需审批版本 + * */ + fun renewalGroupMember( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberSingleRenewalReq + ): Boolean + + /** + * 批量续期用户权限-管理员视角 + * */ + fun batchRenewalGroupMembersFromManager( + userId: String, + projectCode: String, + renewalConditionReq: GroupMemberRenewalConditionReq + ): Boolean + + /** + * 批量交接-管理员视角 + * */ + fun batchHandoverGroupMembersFromManager( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): Boolean + + /** + * 批量交接申请-个人视角 + * */ + fun batchHandoverApplicationFromPersonal( + userId: String, + projectCode: String, + handoverMemberDTO: GroupMemberHandoverConditionReq + ): String + + /** + * 批量移除-管理员视角 + * */ + fun batchDeleteResourceGroupMembersFromManager( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): Boolean + + /** + * 批量退出-个人视角 + * */ + fun batchDeleteResourceGroupMembersFromPersonal( + userId: String, + projectCode: String, + removeMemberDTO: GroupMemberRemoveConditionReq + ): String + + /** + * 退出单个组 + * */ + fun deleteResourceGroupMembers( + userId: String, + projectCode: String, + groupId: Int, + targetMember: ResourceMemberInfo + ): Boolean + + /** + * 批量操作检查 + * */ + fun batchOperateGroupMembersCheck( + userId: String, + projectCode: String, + batchOperateType: BatchOperateType, + conditionReq: GroupMemberCommonConditionReq + ): BatchOperateGroupMemberCheckVo + + /** + * 将用户移出项目-管理员视角 + * */ + fun removeMemberFromProject( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): List + + /** + * 将用户移出项目检查-管理员视角 + * */ + fun removeMemberFromProjectCheck( + userId: String, + projectCode: String, + removeMemberFromProjectReq: RemoveMemberFromProjectReq + ): Boolean + + /** + * 处理交接审批单 + * */ + fun handleHanoverApplication(request: HandoverOverviewUpdateReq): Boolean + + /** + * 批量处理交接审批单 + * */ + fun batchHandleHanoverApplications(request: HandoverOverviewBatchUpdateReq): Boolean + + /** + * 根据资源类型进行分类-交接 + * */ + fun getResourceType2CountOfHandover(queryReq: ResourceType2CountOfHandoverQuery): List + + /** + * 获取交接中授权相关-分为预览/交接单审批两个场景 + * */ + fun listAuthorizationsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 获取交接中用户组相关-分为预览/交接单审批两个场景 + * */ + fun listGroupsOfHandover(queryReq: HandoverDetailsQueryReq): SQLPage + + /** + * 校验是否为项目成员 + * */ + fun isProjectMember( + projectCode: String, + userId: String + ): Boolean +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionProjectService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionProjectService.kt index e40074a0715f..c7b6184a1477 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionProjectService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionProjectService.kt @@ -46,8 +46,19 @@ interface PermissionProjectService { resourceType: String? = null ): List + /** + * 是否有项目访问权限 + * */ fun isProjectUser(userId: String, projectCode: String, group: BkAuthGroup?): Boolean + /** + * 是否是项目成员,即加入了项目 + * */ + fun isProjectMember(userId: String, projectCode: String): Boolean + + /** + * 是否加入项目级的组 + * */ fun checkUserInProjectLevelGroup( userId: String, projectCode: String diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt deleted file mode 100644 index 4a591487e588..000000000000 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupAndMemberFacadeService.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tencent.devops.auth.service.iam - -import com.tencent.devops.auth.pojo.ResourceMemberInfo -import com.tencent.devops.auth.pojo.dto.IamGroupIdsQueryConditionDTO -import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq -import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo -import com.tencent.devops.auth.pojo.vo.MemberGroupCountWithPermissionsVo -import com.tencent.devops.common.api.model.SQLPage - -interface PermissionResourceGroupAndMemberFacadeService { - /** - * 查询成员所在资源用户组详情,直接加入+通过用户组(模板)加入 - * */ - fun getMemberGroupsDetails( - projectId: String, - memberId: String, - resourceType: String? = null, - iamGroupIds: List? = null, - groupName: String? = null, - minExpiredAt: Long? = null, - maxExpiredAt: Long? = null, - relatedResourceType: String? = null, - relatedResourceCode: String? = null, - action: String? = null, - start: Int? = null, - limit: Int? = null - ): SQLPage - - /** - * 获取用户有权限的用户组数量 - * */ - fun getMemberGroupsCount( - projectCode: String, - memberId: String, - groupName: String?, - minExpiredAt: Long?, - maxExpiredAt: Long?, - relatedResourceType: String?, - relatedResourceCode: String?, - action: String? - ): List - - /** - * 根据条件查询组ID - * */ - fun listIamGroupIdsByConditions( - condition: IamGroupIdsQueryConditionDTO - ): List - - /** - * 根据复杂条件进行搜索,用于用户管理界面 - * */ - fun listProjectMembersByComplexConditions( - conditionReq: ProjectMembersQueryConditionReq - ): SQLPage -} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt index 0172b4516c2f..9063615cc7e9 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceGroupPermissionService.kt @@ -91,6 +91,15 @@ interface PermissionResourceGroupPermissionService { action: String ): Boolean + /** + * 是否用户拥有项目级别权限,如整个项目流水线执行权限/项目的管理权限等。 + * */ + fun isGroupsHasProjectLevelPermission( + projectCode: String, + filterIamGroupIds: List, + action: String + ): Boolean + /** * 获取用户组有权限的资源 * */ diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt index 91073660f2fe..7b4aa254ddc3 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt @@ -1,16 +1,8 @@ package com.tencent.devops.auth.service.iam import com.tencent.bk.sdk.iam.dto.manager.ManagerMember -import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO -import com.tencent.devops.auth.pojo.enum.BatchOperateType -import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq -import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq -import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq -import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.ResourceMemberCountVO import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.auth.api.pojo.BkAuthGroup @@ -50,25 +42,6 @@ interface PermissionResourceMemberService { fun addDepartedFlagToMembers(records: List): List - fun listResourceGroupMembers( - projectCode: String, - memberId: String, - resourceType: String? = null, - iamGroupIds: List? = null, - minExpiredAt: Long? = null, - maxExpiredAt: Long? = null, - start: Int? = null, - limit: Int? = null - ): Pair> - - /** - * 获取用户在该项目加入的组 - * */ - fun listMemberGroupIdsInProject( - projectCode: String, - memberId: String - ): List - fun batchDeleteResourceGroupMembers( projectCode: String, iamGroupId: Int, @@ -76,43 +49,12 @@ interface PermissionResourceMemberService { departments: List? = emptyList() ): Boolean - fun batchDeleteResourceGroupMembers( - userId: String, - projectCode: String, - removeMemberDTO: GroupMemberCommonConditionReq - ): Boolean - fun deleteIamGroupMembers( groupId: Int, type: String, memberIds: List ): Boolean - fun batchHandoverGroupMembers( - userId: String, - projectCode: String, - handoverMemberDTO: GroupMemberHandoverConditionReq - ): Boolean - - fun batchOperateGroupMembersCheck( - userId: String, - projectCode: String, - batchOperateType: BatchOperateType, - conditionReq: GroupMemberCommonConditionReq - ): BatchOperateGroupMemberCheckVo - - fun removeMemberFromProject( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): List - - fun removeMemberFromProjectCheck( - userId: String, - projectCode: String, - removeMemberFromProjectReq: RemoveMemberFromProjectReq - ): Boolean - fun roleCodeToIamGroupId( projectCode: String, roleCode: String @@ -134,25 +76,12 @@ interface PermissionResourceMemberService { memberRenewalDTO: GroupMemberRenewalDTO ): Boolean - // 无需审批版本 - fun renewalGroupMember( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberSingleRenewalReq - ): Boolean - fun renewalIamGroupMembers( groupId: Int, members: List, expiredAt: Long ): Boolean - fun batchRenewalGroupMembers( - userId: String, - projectCode: String, - renewalConditionReq: GroupMemberRenewalConditionReq - ): Boolean - fun addGroupMember( projectCode: String, memberId: String, diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt index e81196b7eec4..c9c9e261ce96 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceValidateService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.service.iam import com.tencent.devops.auth.pojo.dto.PermissionBatchValidateDTO +import com.tencent.devops.auth.pojo.enum.OperateChannel interface PermissionResourceValidateService { fun batchValidateUserResourcePermission( @@ -46,4 +47,14 @@ interface PermissionResourceValidateService { resourceType: String, resourceCode: String ): Boolean + + /** + * 根据渠道来校验用户权限,主要用户管理界面/个人视角 + */ + fun validateUserProjectPermissionByChannel( + userId: String, + projectCode: String, + operateChannel: OperateChannel, + targetMemberId: String + ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/CronSyncGroupMembersExpiredTimeLock.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/CronSyncGroupMembersExpiredTimeLock.kt index 4f02ae448bcb..e6faff66647c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/CronSyncGroupMembersExpiredTimeLock.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/CronSyncGroupMembersExpiredTimeLock.kt @@ -34,8 +34,8 @@ class CronSyncGroupMembersExpiredTimeLock(redisOperation: RedisOperation) : RedisLock( redisOperation = redisOperation, lockKey = "cron.sync.group.member.expired.lock", - // 2小时,防止服务重启,锁未释放 - expiredTimeInSeconds = 7200000 + // 1小时,防止服务重启,锁未释放 + expiredTimeInSeconds = 3600000 ) { override fun decorateKey(key: String): String { return key diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt new file mode 100644 index 000000000000..741873edb556 --- /dev/null +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/lock/HandleHandoverApplicationLock.kt @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.auth.service.lock + +import com.tencent.devops.common.redis.RedisLock +import com.tencent.devops.common.redis.RedisOperation + +class HandleHandoverApplicationLock( + redisOperation: RedisOperation, + flowNo: String +) : + RedisLock( + redisOperation = redisOperation, + lockKey = "auth.handover.$flowNo.lock", + expiredTimeInSeconds = 1800 + ) { + override fun decorateKey(key: String): String { + return key + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/AbMigratePolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/AbMigratePolicyServiceTest.kt index c8cc6766af6c..ac077b5ea459 100644 --- a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/AbMigratePolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/AbMigratePolicyServiceTest.kt @@ -37,7 +37,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupConfigDao import com.tencent.devops.auth.dao.AuthResourceGroupDao import com.tencent.devops.auth.provider.rbac.pojo.migrate.MigrateTaskDataResult import com.tencent.devops.auth.provider.rbac.service.AuthResourceCodeConverter -import com.tencent.devops.auth.provider.rbac.service.RbacCacheService +import com.tencent.devops.auth.provider.rbac.service.RbacCommonService import com.tencent.devops.auth.provider.rbac.service.migrate.AbMigratePolicyService import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateIamApiService import com.tencent.devops.auth.provider.rbac.service.migrate.MigrateResourceCodeConverter @@ -63,7 +63,7 @@ open class AbMigratePolicyServiceTest : BkCiAbstractTest() { val migrateIamApiService: MigrateIamApiService = mockk() val authMigrationDao: AuthMigrationDao = mockk() val permissionService: PermissionService = mockk() - val rbacCacheService: RbacCacheService = mockk() + val rbacCommonService: RbacCommonService = mockk() val migrateResourceCodeConverter: MigrateResourceCodeConverter = mockk() val authResourceCodeConverter: AuthResourceCodeConverter = mockk() val deptService: DeptService = mockk() diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV0PolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV0PolicyServiceTest.kt index 5564b1b2d8dc..96221ec2e6d4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV0PolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV0PolicyServiceTest.kt @@ -56,7 +56,7 @@ class MigrateV0PolicyServiceTest : AbMigratePolicyServiceTest() { migrateIamApiService = migrateIamApiService, authResourceCodeConverter = authResourceCodeConverter, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, authMigrationDao = authMigrationDao, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, diff --git a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV3PolicyServiceTest.kt b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV3PolicyServiceTest.kt index f18ee9cf8d2f..bf4e94b3d767 100644 --- a/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV3PolicyServiceTest.kt +++ b/src/backend/ci/core/auth/biz-auth/src/test/kotlin/com/tencent/devops/auth/rbac/service/migrate/MigrateV3PolicyServiceTest.kt @@ -55,7 +55,7 @@ class MigrateV3PolicyServiceTest : AbMigratePolicyServiceTest() { migrateIamApiService = migrateIamApiService, authResourceCodeConverter = authResourceCodeConverter, permissionService = permissionService, - rbacCacheService = rbacCacheService, + rbacCommonService = rbacCommonService, authMigrationDao = authMigrationDao, deptService = deptService, permissionResourceGroupPermissionService = permissionResourceGroupPermissionService, diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt index 22c0d182e12a..0338fc5c4aa4 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/auth/Header.kt @@ -121,3 +121,4 @@ const val AUTH_HEADER_DEVOPS_STORE_CODE: String = "X-DEVOPS-STORE-CODE" const val AUTH_HEADER_DEVOPS_STORE_TYPE: String = "X-DEVOPS-STORE-TYPE" const val AUTH_HEADER_DEVOPS_STORE_VERSION: String = "X-DEVOPS-STORE-VERSION" const val AUTH_HEADER_DEVOPS_SIGN_FILE_NAME: String = "X-DEVOPS-SIGN-FILE-NAME" +const val AUTH_HEADER_DEVOPS_ENV: String = "X-DEVOPS-ENV" diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/check/Preconditions.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/check/Preconditions.kt index a35e11a61d3b..b452fd68916a 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/check/Preconditions.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/check/Preconditions.kt @@ -27,6 +27,8 @@ package com.tencent.devops.common.api.check +import com.tencent.devops.common.api.exception.ParamBlankException + /** * 前置条件校验工具类 */ @@ -36,10 +38,27 @@ object Preconditions { * 检查对象[obj]不为空,否则抛出指定的异常[exception] */ @Throws(Exception::class) - fun checkNotNull(obj: Any?, exception: Exception) { + fun checkNotNull(obj: T?, exception: () -> Exception): T { if (obj == null) { - throw exception + throw exception() } + return obj + } + + /** + * 检查对象[obj]不为空,抛出默认错误内容 + */ + @Throws(Exception::class) + fun checkNotNull(obj: T?): T { + return checkNotNull(obj) { ParamBlankException("Required value was null.") } + } + + /** + * 检查对象[obj]不为空, 为空则抛出[message]内容提醒上游 + */ + @Throws(Exception::class) + fun checkNotNull(obj: T?, message: String): T { + return checkNotNull(obj) { ParamBlankException(message) } } /** diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt new file mode 100644 index 000000000000..b11db310a546 --- /dev/null +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/enums/ScmCode.kt @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.common.api.enums + +/** + * 代码库类型 + */ +enum class ScmCode(val scmName: String, val value: String) { + TGIT("GIT", "TGIT"), // 内部工蜂 + GITHUB("GITHUB", "GITHUB"), // github + TGIT_CO("TGIT_CO", "TGIT-CO"); // github + + fun convertScmType(): ScmType { + return when (this) { + TGIT -> ScmType.CODE_GIT + GITHUB -> ScmType.GITHUB + TGIT_CO -> ScmType.CODE_TGIT + } + } +} diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/pojo/ErrorType.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/pojo/ErrorType.kt index e2f7b8d0572f..1f453fec3be0 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/pojo/ErrorType.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/pojo/ErrorType.kt @@ -39,7 +39,8 @@ enum class ErrorType( SYSTEM("system", 0), // 0 系统运行报错 USER("user", 1), // 1 用户配置报错 THIRD_PARTY("thirdParty", 2), // 2 第三方系统接入错误 - PLUGIN("plugin", 3); // 3 插件执行错误 + PLUGIN("plugin", 3), // 3 插件执行错误 + BUILD_MACHINE("buildMachine", 4); // 4 构建机运行报错 companion object { @@ -57,6 +58,7 @@ enum class ErrorType( 0 -> SYSTEM 1 -> USER 2 -> THIRD_PARTY + 4 -> BUILD_MACHINE else -> PLUGIN } } diff --git a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/client/BkRepoClient.kt b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/client/BkRepoClient.kt index dee6e2c49048..f1f4e6cb4dfc 100644 --- a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/client/BkRepoClient.kt +++ b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/client/BkRepoClient.kt @@ -164,7 +164,9 @@ class BkRepoClient constructor( fun enableProject(userId: String, projectId: String, enabled: Boolean): Boolean { logger.info("enableProject, userId: $userId, projectId: $projectId, enabled: $enabled") - val requestData = BKRepoProjectUpdateRequest(metadata = listOf(ProjectMetadata(key = "enabled", value = enabled))) + val requestData = BKRepoProjectUpdateRequest( + metadata = listOf(ProjectMetadata(key = "enabled", value = enabled)) + ) val request = Request.Builder() .url("${getGatewayUrl()}/bkrepo/api/service/repository/api/project/$projectId") .headers(getCommonHeaders(SYSTEM_USER, projectId).toHeaders()) @@ -214,7 +216,7 @@ class BkRepoClient constructor( fun setMetadata(userId: String, projectId: String, repoName: String, path: String, metadata: Map) { logger.info( "setMetadata, userId: $userId, projectId: $projectId, repoName: $repoName, path: $path," + - " metadata: $metadata" + " metadata: $metadata" ) val url = "${getGatewayUrl()}/bkrepo/api/service/repository/api/metadata/$projectId/$repoName/$path" val requestData = UserMetadataSaveRequest( @@ -249,10 +251,10 @@ class BkRepoClient constructor( ): List { logger.info( "listFile, userId: $userId, projectId: $projectId, repoName: $repoName, path: $path," + - " includeFolders: $includeFolders, deep: $deep" + " includeFolders: $includeFolders, deep: $deep" ) val url = "${getGatewayUrl()}/bkrepo/api/service/generic/list/$projectId/$repoName/$path" + - "?deep=$deep&includeFolder=$includeFolders" + "?deep=$deep&includeFolder=$includeFolders" val request = Request.Builder() .url(url) .headers(getCommonHeaders(userId, projectId).toHeaders()) @@ -274,12 +276,12 @@ class BkRepoClient constructor( ): Page { logger.info( "listFilePage, userId: $userId, projectId: $projectId, repoName: $repoName, path: $path," + - " includeFolders: $includeFolders, deep: $deep, page: $page, pageSize: $pageSize" + " includeFolders: $includeFolders, deep: $deep, page: $page, pageSize: $pageSize" ) val direction = if (modifiedTimeDesc) Direction.DESC.name else Direction.ASC.name val url = "${getGatewayUrl()}/bkrepo/api/service/repository/api/node/page/$projectId/$repoName/$path" + - "?deep=$deep&includeFolder=$includeFolders&includeMetadata=true&pageNumber=$page&pageSize=$pageSize" + - "&sortProperty=lastModifiedDate&direction=$direction" + "?deep=$deep&includeFolder=$includeFolders&includeMetadata=true&pageNumber=$page&pageSize=$pageSize" + + "&sortProperty=lastModifiedDate&direction=$direction" val request = Request.Builder() .url(url) .headers(getCommonHeaders(userId, projectId).toHeaders()) @@ -300,7 +302,7 @@ class BkRepoClient constructor( ) { logger.info( "uploadFile, userId: $userId, projectId: $projectId, repoName: $repoName, path: $path," + - " fileSizeLimitInMB: $fileSizeLimitInMB" + " fileSizeLimitInMB: $fileSizeLimitInMB" ) if (PathUtil.isFolder(path)) { throw ErrorCodeException(errorCode = INVALID_CUSTOM_ARTIFACTORY_PATH) @@ -353,7 +355,7 @@ class BkRepoClient constructor( fun uploadLocalFile(userId: String, projectId: String, repoName: String, path: String, file: File) { logger.info( "uploadLocalFile, userId: $userId, projectId: $projectId, repoName: $repoName, path: $path," + - " localFile: ${file.canonicalPath}" + " localFile: ${file.canonicalPath}" ) uploadLocalFile( userId = userId, @@ -380,7 +382,7 @@ class BkRepoClient constructor( ) { logger.info( "uploadLocalFile, projectId: $projectId, repoName: $repoName, path: $path," + - " localFile: ${file.canonicalPath}" + " localFile: ${file.canonicalPath}" ) val gateway = gatewayUrl ?: getGatewayUrl() val repoUrlPrefix = if (gatewayFlag) "$gateway/bkrepo/api/service/generic" else bkrepoApiUrl @@ -436,7 +438,7 @@ class BkRepoClient constructor( fun move(userId: String, projectId: String, repoName: String, fromPath: String, toPath: String) { logger.info( "move, userId: $userId, projectId: $projectId, repoName: $repoName, fromPath: $fromPath," + - " toPath: $toPath" + " toPath: $toPath" ) val url = "${getGatewayUrl()}/bkrepo/api/service/repository/api/node/move" val requestData = UserNodeMoveCopyRequest( @@ -468,7 +470,7 @@ class BkRepoClient constructor( ) { logger.info( "copy, userId: $userId, fromProject: $fromProject, fromRepo: $fromRepo, fromPath: $fromPath," + - " toProject: $toProject, toRepo: $toRepo, toPath: $toPath" + " toProject: $toProject, toRepo: $toRepo, toPath: $toPath" ) val url = "${getGatewayUrl()}/bkrepo/api/service/repository/api/node/copy" val requestData = UserNodeMoveCopyRequest( @@ -493,7 +495,7 @@ class BkRepoClient constructor( fun rename(userId: String, projectId: String, repoName: String, fromPath: String, toPath: String) { logger.info( "rename, userId: $userId, projectId: $projectId, repoName: $repoName, fromPath: $fromPath," + - " toPath: $toPath" + " toPath: $toPath" ) val url = "${getGatewayUrl()}/bkrepo/api/service/repository/api/node/rename" val requestData = UserNodeRenameRequest(projectId, repoName, fromPath, toPath) @@ -542,7 +544,7 @@ class BkRepoClient constructor( ): List { logger.info( "matchBkRepoFile, userId: $userId, srcPath: $srcPath, projectId: $projectId," + - " pipelineId: $pipelineId, buildId: $buildId, isCustom: $isCustom" + " pipelineId: $pipelineId, buildId: $buildId, isCustom: $isCustom" ) val repoName: String val filePath: String @@ -639,7 +641,7 @@ class BkRepoClient constructor( ): List { logger.info( "listFileByPattern, userId: $userId, projectId: $projectId, pipelineId: $pipelineId," + - " buildId: $buildId, repoName: $repoName, pathPattern: $pathPattern" + " buildId: $buildId, repoName: $repoName, pathPattern: $pathPattern" ) return if (pathPattern.endsWith("/")) { val path = if (repoName == "pipeline") { @@ -682,7 +684,7 @@ class BkRepoClient constructor( ): List { logger.info( "downloadFileByPattern, userId: $userId, projectId: $projectId, pipelineId: $pipelineId," + - " buildId: $buildId, repoName: $repoName, pathPattern: $pathPattern, destPath: $destPath" + " buildId: $buildId, repoName: $repoName, pathPattern: $pathPattern, destPath: $destPath" ) val fileList = listFileByPattern( userId, @@ -718,8 +720,8 @@ class BkRepoClient constructor( ): String { logger.info( "createShareUri, creatorId: $creatorId, projectId: $projectId, repoName: $repoName, " + - "fullPath: $fullPath, downloadUsers: $downloadUsers, downloadIps: $downloadIps, " + - "timeoutInSeconds: $timeoutInSeconds" + "fullPath: $fullPath, downloadUsers: $downloadUsers, downloadIps: $downloadIps, " + + "timeoutInSeconds: $timeoutInSeconds" ) val repoUrlPrefix = if (bkrepoPrefixUrl.isNullOrBlank()) { "${getGatewayUrl()}/bkrepo/api/service" @@ -778,7 +780,7 @@ class BkRepoClient constructor( ): ApkDefenderTasks { logger.info( "apkDefender , projectId: $projectId , repoName: $repoName , fullPath: $fullPath , " + - "userIds: $userIds, batchSize: $batchSize" + "userIds: $userIds, batchSize: $batchSize" ) val url = "${getGatewayUrl()}/bkrepo/api/external/analyst/api/ext/apk/defender" val apkDefenderRequest = ApkDefenderRequest( @@ -844,8 +846,8 @@ class BkRepoClient constructor( ): List { logger.info( "createTemporaryAccessUrl, userId: $userId, projectId: $projectId, repoName: $repoName, " + - "fullPathSet: $fullPathSet, downloadUsersSet: $downloadUsersSet, downloadIps: $downloadIpsSet," + - " timeoutInSeconds: $timeoutInSeconds" + "fullPathSet: $fullPathSet, downloadUsersSet: $downloadUsersSet, downloadIps: $downloadIpsSet," + + " timeoutInSeconds: $timeoutInSeconds" ) val url = "${getGatewayUrl()}/bkrepo/api/service/generic/temporary/url/create" val requestData = TemporaryTokenCreateRequest( @@ -878,7 +880,7 @@ class BkRepoClient constructor( ): QueryData { logger.info( "queryByRepoAndMetadata, userId: $userId, projectId: $projectId, repoNames: $repoNames," + - " fileNames: $fileNames, metadata: $metadata, page: $page, pageSize: $pageSize" + " fileNames: $fileNames, metadata: $metadata, page: $page, pageSize: $pageSize" ) val projectRule = Rule.QueryRule("projectId", projectId, OperationType.EQ) val ruleList = mutableListOf(projectRule, Rule.QueryRule("folder", false, OperationType.EQ)) @@ -915,8 +917,8 @@ class BkRepoClient constructor( ): QueryData { logger.info( "queryByPathEqOrNameMatchOrMetadataEqAnd, userId: $userId, projectId: $projectId," + - " repoNames: $repoNames, filePaths: $filePaths, fileNames: $fileNames, metadata: $metadata," + - " page: $page, pageSize: $pageSize" + " repoNames: $repoNames, filePaths: $filePaths, fileNames: $fileNames, metadata: $metadata," + + " page: $page, pageSize: $pageSize" ) val projectRule = Rule.QueryRule("projectId", projectId, OperationType.EQ) val repoRule = Rule.QueryRule("repoName", repoNames, OperationType.IN) @@ -963,8 +965,8 @@ class BkRepoClient constructor( ): QueryData { logger.info( "queryByPathNamePairOrMetadataEqAnd, userId: $userId, projectId: $projectId," + - " repoNames: $repoNames, pathNamePairs: $pathNamePairs, metadata: $metadata," + - " page: $page, pageSize: $pageSize" + " repoNames: $repoNames, pathNamePairs: $pathNamePairs, metadata: $metadata," + + " page: $page, pageSize: $pageSize" ) val projectRule = Rule.QueryRule("projectId", projectId, OperationType.EQ) val repoRule = Rule.QueryRule("repoName", repoNames, OperationType.IN) @@ -1000,7 +1002,7 @@ class BkRepoClient constructor( ): QueryData { logger.info( "queryByPattern, userId: $userId, projectId: $projectId, repoNames: $repoNames," + - " fullPathPatterns: $fullPathPatterns, metadata: $metadata" + " fullPathPatterns: $fullPathPatterns, metadata: $metadata" ) val projectRule = Rule.QueryRule("projectId", projectId, OperationType.EQ) val repoRule = Rule.QueryRule("repoName", repoNames, OperationType.IN) @@ -1033,7 +1035,7 @@ class BkRepoClient constructor( ): QueryData { logger.info( "listFileByQuery, userId: $userId, projectId: $projectId, repoName: $repoName," + - " path: $path, includeFolders: $includeFolders" + " path: $path, includeFolders: $includeFolders" ) val projectRule = Rule.QueryRule("projectId", projectId, OperationType.EQ) val repoRule = Rule.QueryRule("repoName", repoName, OperationType.EQ) @@ -1082,7 +1084,7 @@ class BkRepoClient constructor( metadata: Map? = null ): PackageVersionInfo { val url = "${getGatewayUrl()}/bkrepo/api/service/docker/ext/version/detail/$projectId/$repoName" + - "?packageKey=$packageKey&version=$version" + "?packageKey=$packageKey&version=$version" val request = Request.Builder().url(url).headers(getCommonHeaders(userId, projectId).toHeaders()).get().build() return doRequest(request).resolveResponse>()!!.data!! } diff --git a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt index 9d88f752de6b..3657af9a6d64 100644 --- a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt +++ b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/BKRepoProjectUpdateRequest.kt @@ -12,5 +12,5 @@ data class BKRepoProjectUpdateRequest( @get:Schema(title = "项目新建仓库默认使用的存储") val credentialsKey: String? = null, @get:Schema(title = "设置项目新建仓库默认使用默认存储") - val useDefaultCredentialsKey: Boolean? = false, + val useDefaultCredentialsKey: Boolean? = false ) diff --git a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt index f53d41090d8a..2c31b9a545ba 100644 --- a/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt +++ b/src/backend/ci/core/common/common-archive/src/main/kotlin/com/tencent/devops/common/archive/pojo/ProjectMetadata.kt @@ -8,5 +8,5 @@ data class ProjectMetadata( /** * 元数据值 */ - var value: Any, + var value: Any ) diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt index 835cf51999ab..b331faa1d4b6 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/ActionId.kt @@ -2,6 +2,7 @@ package com.tencent.devops.common.auth.api object ActionId { // 项目 + const val PROJECT_VISIT = "project_visit" const val PROJECT_CREATE = "project_create" const val PROJECT_EDIT = "project_edit" const val PROJECT_ENABLE = "project_enable" diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ProjectConditionDTO.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ProjectConditionDTO.kt index e11c6c31e403..baa58c16a502 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ProjectConditionDTO.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ProjectConditionDTO.kt @@ -32,5 +32,7 @@ data class ProjectConditionDTO( @get:Schema(title = "是否启用") val enabled: Boolean? = null, @get:Schema(title = "渠道代码") - val channelCode: String? = null + val channelCode: String? = null, + @get:Schema(title = "remoteDev相关") + val queryRemoteDevFlag: Boolean? = null ) diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt index d5f8241e2204..9ec5d746ead8 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationConditionRequest.kt @@ -12,12 +12,18 @@ open class ResourceAuthorizationConditionRequest( open val resourceType: String? = null, @get:Schema(title = "资源名称") open val resourceName: String? = null, + @get:Schema(title = "过滤资源ID列表") + open var filterResourceCodes: List? = null, + @get:Schema(title = "排除资源ID列表") + open var excludeResourceCodes: List? = null, @get:Schema(title = "授予人") open val handoverFrom: String? = null, @get:Schema(title = "greaterThanHandoverTime") open val greaterThanHandoverTime: Long? = null, @get:Schema(title = "lessThanHandoverTime") open val lessThanHandoverTime: Long? = null, + @get:Schema(title = "是否查询交接中单据") + open val queryHandover: Boolean? = null, @Parameter(description = "第几页", required = false) open val page: Int? = null, @Parameter(description = "每页条数", required = false) diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt index 21fd1979736e..633031a4fc86 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationHandoverConditionRequest.kt @@ -12,6 +12,10 @@ data class ResourceAuthorizationHandoverConditionRequest( override val resourceType: String, @get:Schema(title = "资源名称") override val resourceName: String? = null, + @get:Schema(title = "过滤资源ID列表") + override var filterResourceCodes: List? = null, + @get:Schema(title = "排除资源ID列表") + override var excludeResourceCodes: List? = null, @get:Schema(title = "授予人") override val handoverFrom: String? = null, @get:Schema(title = "greaterThanHandoverTime") @@ -38,6 +42,8 @@ data class ResourceAuthorizationHandoverConditionRequest( projectCode = projectCode, resourceType = resourceType, resourceName = resourceName, + filterResourceCodes = filterResourceCodes, + excludeResourceCodes = excludeResourceCodes, handoverFrom = handoverFrom, greaterThanHandoverTime = greaterThanHandoverTime, lessThanHandoverTime = lessThanHandoverTime, diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt index 0c396b8c1982..179fc004daa9 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/ResourceAuthorizationResponse.kt @@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "资源授权返回体") @Suppress("LongParameterList") data class ResourceAuthorizationResponse( + @get:Schema(title = "ID") + val id: Long? = null, @get:Schema(title = "项目ID") val projectCode: String, @get:Schema(title = "资源类型") @@ -20,5 +22,9 @@ data class ResourceAuthorizationResponse( @get:Schema(title = "授予人中文名称") val handoverFromCnName: String? = null, @get:Schema(title = "是否有执行权限") - val executePermission: Boolean? = null + val executePermission: Boolean? = null, + @get:Schema(title = "是否正在交接,用于我的授权界面") + val beingHandover: Boolean? = null, + @get:Schema(title = "交接审批人") + val approver: String? = null ) diff --git a/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeCCAutoConfiguration.kt b/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeCCAutoConfiguration.kt index 8a882a452d78..c5a92c87b188 100644 --- a/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeCCAutoConfiguration.kt +++ b/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeCCAutoConfiguration.kt @@ -46,15 +46,12 @@ class CodeCCAutoConfiguration { codeccApiProxyGateWay: String = "", @Value("\${codecc.host:}") - codeccHost: String = "", + codeccHost: String = "" - @Value("\${codecc.gray.projectId:}") - codeccGrayProjectId: String? = null ): CodeccApi = CodeccApi( codeccApiUrl = codeccApiGateWay, codeccApiProxyUrl = codeccApiProxyGateWay, - codeccHost = codeccHost, - codeccGrayProjectId = codeccGrayProjectId + codeccHost = codeccHost ) } diff --git a/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeccApi.kt b/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeccApi.kt index 3908ab70d7ea..8ab4e2a79d74 100644 --- a/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeccApi.kt +++ b/src/backend/ci/core/common/common-codecc/src/main/kotlin/com/tencent/devops/plugin/codecc/CodeccApi.kt @@ -28,30 +28,34 @@ package com.tencent.devops.plugin.codecc import com.fasterxml.jackson.module.kotlin.readValue +import com.tencent.devops.common.api.auth.AUTH_HEADER_CODECC_OPENAPI_TOKEN import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_PROJECT_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID -import com.tencent.devops.common.api.auth.AUTH_HEADER_PROJECT_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_GATEWAY_TAG import com.tencent.devops.common.api.exception.RemoteServiceException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.OkhttpUtils import com.tencent.devops.plugin.codecc.pojo.CodeccMeasureInfo +import java.net.URLEncoder +import javax.ws.rs.HttpMethod import okhttp3.Headers.Companion.toHeaders import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.slf4j.LoggerFactory -import java.net.URLEncoder -import javax.ws.rs.HttpMethod +import org.springframework.beans.factory.annotation.Value @Suppress("ALL") class CodeccApi( private val codeccApiUrl: String, private val codeccApiProxyUrl: String, - private val codeccHost: String, - private val codeccGrayProjectId: String? = null + private val codeccHost: String ) { + @Value("\${codecc.openapi.token:#{null}}") + private val codeccOpenApiToken: String = "" + companion object { private val objectMapper = JsonUtil.getObjectMapper() private val logger = LoggerFactory.getLogger(CodeccApi::class.java) @@ -184,12 +188,13 @@ class CodeccApi( return objectMapper.readValue(result) } - fun getCodeccOpensourceMeasurement(atomCodeSrc: String): Result> { - val url = "http://$codeccHost/ms/defect/api/service/defect/opensource/measurement?url=$atomCodeSrc" + fun getCodeccOpensourceMeasurement(atomCodeSrc: String, tag: String? = null): Result> { + val url = "http://$codeccHost/ms/openapi/api/open/v2/defect/opensource/measurement?url=$atomCodeSrc" val headers = mutableMapOf() - if (!codeccGrayProjectId.isNullOrBlank()) { - headers[AUTH_HEADER_PROJECT_ID] = codeccGrayProjectId + if (!tag.isNullOrBlank()) { + headers[AUTH_HEADER_GATEWAY_TAG] = tag } + headers[AUTH_HEADER_CODECC_OPENAPI_TOKEN] = codeccOpenApiToken val httpReq = Request.Builder() .url(url) .headers(headers.toHeaders()) diff --git a/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt b/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt index b92f504976e2..e8019a55f82a 100644 --- a/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt +++ b/src/backend/ci/core/common/common-db-sharding/src/main/kotlin/com/tencent/devops/common/db/config/BkShardingDataSourceConfiguration.kt @@ -43,6 +43,8 @@ import com.tencent.devops.common.db.pojo.TableShardingStrategyEnum import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory import io.micrometer.core.instrument.MeterRegistry +import java.util.Properties +import javax.sql.DataSource import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration @@ -62,8 +64,6 @@ import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Primary import org.springframework.core.Ordered import org.springframework.transaction.annotation.EnableTransactionManagement -import java.util.Properties -import javax.sql.DataSource @Suppress("LongParameterList", "MagicNumber", "ComplexMethod") @Configuration @@ -122,6 +122,9 @@ class BkShardingDataSourceConfiguration { @Value("\${sharding.tableShardingStrategy.defaultShardingNum:#{5}}") private val defaultTableShardingNum: Int = 5 + @Value("\${spring.datasource.connectionTestQuery:select 1;}") + private lateinit var dataSourceConnectionTestQuery: String + private fun dataSourceMap( dataSourcePrefixName: String, dataSourceConfigs: List, @@ -165,6 +168,7 @@ class BkShardingDataSourceConfiguration { connectionInitSql = datasourceInitSql leakDetectionThreshold = datasourceLeakDetectionThreshold metricsTrackerFactory = MicrometerMetricsTrackerFactory(metricsRegistry) + connectionTestQuery = dataSourceConnectionTestQuery } } diff --git a/src/backend/ci/core/common/common-db/src/main/kotlin/com/tencent/devops/common/db/DBAutoConfiguration.kt b/src/backend/ci/core/common/common-db/src/main/kotlin/com/tencent/devops/common/db/DBAutoConfiguration.kt index a18cd456a844..49f900611e5b 100644 --- a/src/backend/ci/core/common/common-db/src/main/kotlin/com/tencent/devops/common/db/DBAutoConfiguration.kt +++ b/src/backend/ci/core/common/common-db/src/main/kotlin/com/tencent/devops/common/db/DBAutoConfiguration.kt @@ -30,6 +30,7 @@ package com.tencent.devops.common.db import com.mysql.cj.jdbc.Driver import com.tencent.devops.common.db.config.DBBaseConfiguration import com.zaxxer.hikari.HikariDataSource +import javax.sql.DataSource import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.AutoConfigureBefore import org.springframework.boot.autoconfigure.AutoConfigureOrder @@ -42,7 +43,6 @@ import org.springframework.context.annotation.Primary import org.springframework.context.annotation.PropertySource import org.springframework.core.Ordered import org.springframework.transaction.annotation.EnableTransactionManagement -import javax.sql.DataSource /** * @@ -58,21 +58,31 @@ class DBAutoConfiguration { @Value("\${spring.datasource.url:#{null}}") private val datasourceUrl: String? = null + @Value("\${spring.datasource.username:#{null}}") private val datasourceUsername: String? = null + @Value("\${spring.datasource.password:#{null}}") private val datasourcePassword: String? = null + @Value("\${spring.datasource.initSql:#{null}}") private val datasourceInitSql: String? = null + @Value("\${spring.datasource.leakDetectionThreshold:#{0}}") private val datasouceLeakDetectionThreshold: Long = 0 + @Value("\${spring.datasource.minimumIdle:#{1}}") private val datasourceMinimumIdle: Int = 1 + @Value("\${spring.datasource.maximumPoolSize:#{50}}") private val datasourceMaximumPoolSize: Int = 50 + @Value("\${spring.datasource.idleTimeout:#{60000}}") private val datasourceIdleTimeout: Long = 60000 + @Value("\${spring.datasource.connectionTestQuery:select 1;}") + private lateinit var dataSourceConnectionTestQuery: String + @Bean @Primary fun dataSource(): DataSource { @@ -90,6 +100,7 @@ class DBAutoConfiguration { idleTimeout = datasourceIdleTimeout connectionInitSql = datasourceInitSql leakDetectionThreshold = datasouceLeakDetectionThreshold + connectionTestQuery = dataSourceConnectionTestQuery } } } diff --git a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt index 9f6898a05e20..385e4304c00e 100644 --- a/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt +++ b/src/backend/ci/core/common/common-dispatch-sdk/src/main/kotlin/com/tencent/devops/common/dispatch.sdk/service/DispatchService.kt @@ -65,9 +65,10 @@ import com.tencent.devops.process.engine.pojo.PipelineBuildContainer import com.tencent.devops.process.engine.pojo.PipelineBuildTask import com.tencent.devops.process.pojo.mq.PipelineAgentShutdownEvent import com.tencent.devops.process.pojo.mq.PipelineAgentStartupEvent +import feign.RetryableException +import org.slf4j.LoggerFactory import java.util.Date import java.util.concurrent.TimeUnit -import org.slf4j.LoggerFactory @Suppress("LongParameterList", "TooManyFunctions") class DispatchService constructor( @@ -172,7 +173,7 @@ class DispatchService constructor( executeCount: Int?, logTag: String? ): Boolean { - val (startBuildTask, buildContainer) = getContainerStartupInfo( + val (startBuildTask, buildContainer) = getContainerStartupInfoWithRetry( projectId = projectId, buildId = buildId, containerId = containerId, @@ -251,7 +252,7 @@ class DispatchService constructor( ) { logger.warn("[$buildId|$vmSeqId] Container startup failure") try { - val (startBuildTask, buildContainer) = getContainerStartupInfo( + val (startBuildTask, buildContainer) = getContainerStartupInfoWithRetry( projectId = projectId, buildId = buildId, containerId = vmSeqId, @@ -318,6 +319,41 @@ class DispatchService constructor( } } + /** + * 针对服务间调用出现 Connection refused 的异常,进行重试 + */ + private fun getContainerStartupInfoWithRetry( + projectId: String, + buildId: String, + containerId: String, + logTag: String?, + retryTimes: Int = RETRY_TIMES + ): Pair { + try { + return getContainerStartupInfo( + projectId = projectId, + buildId = buildId, + containerId = containerId, + logTag = logTag + ) + } catch (e: RetryableException) { + if (retryTimes > 0) { + logger.warn("[$buildId]|[$containerId]| getContainerStartupInfo failed, " + + "retryTimes=$retryTimes", e.message) + Thread.sleep(1000) + return getContainerStartupInfoWithRetry( + projectId = projectId, + buildId = buildId, + containerId = containerId, + logTag = logTag, + retryTimes = retryTimes - 1 + ) + } else { + throw e + } + } + } + private fun getContainerStartupInfo( projectId: String, buildId: String, @@ -416,5 +452,7 @@ class DispatchService constructor( companion object { private val logger = LoggerFactory.getLogger(DispatchService::class.java) + + private const val RETRY_TIMES = 3 } } diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt index 9e80909c61fc..ea0ff3e36820 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/VariableTransfer.kt @@ -34,6 +34,7 @@ import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType import com.tencent.devops.common.pipeline.pojo.BuildContainerType import com.tencent.devops.common.pipeline.pojo.BuildFormProperty import com.tencent.devops.common.pipeline.pojo.BuildFormValue +import com.tencent.devops.common.pipeline.utils.CascadePropertyUtils import com.tencent.devops.process.utils.FIXVERSION import com.tencent.devops.process.utils.MAJORVERSION import com.tencent.devops.process.utils.MINORVERSION @@ -91,6 +92,12 @@ class VariableTransfer { type = VariablePropType.GIT_REF.value, repoHashId = it.repoHashId ) + CascadePropertyUtils.supportCascadeParam(it.type) -> { + // 级联选择器类型变量 + VariableProps( + type = VariablePropType.REPO_REF.value + ) + } it.type == BuildFormPropertyType.MULTIPLE -> VariableProps( type = VariablePropType.CHECKBOX.value, @@ -132,7 +139,11 @@ class VariableTransfer { } val const = it.constant.nullIfDefault(false) result[it.id] = Variable( - value = it.defaultValue.toString(), + value = if (CascadePropertyUtils.supportCascadeParam(it.type)) { + CascadePropertyUtils.parseDefaultValue(it.id, it.defaultValue, it.type) + } else { + it.defaultValue.toString() + }, readonly = if (const == true) null else it.readOnly.nullIfDefault(false), allowModifyAtStartup = if (const != true) it.required.nullIfDefault(true) else null, const = const, @@ -207,8 +218,13 @@ class VariableTransfer { required = variable.allowModifyAtStartup ?: true, constant = variable.const ?: false, type = type, - defaultValue = when (type) { - BuildFormPropertyType.BOOLEAN -> variable.value?.toBoolean() ?: false + defaultValue = when { + type == BuildFormPropertyType.BOOLEAN -> + (variable.value as String?)?.toBoolean() ?: false + + CascadePropertyUtils.supportCascadeParam(type) -> + variable.value ?: mapOf() + else -> variable.value ?: "" }, options = variable.props?.options?.map { diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/inner/TransferCreatorImpl.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/inner/TransferCreatorImpl.kt index 97a2d5096441..0cc0c57145d0 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/inner/TransferCreatorImpl.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/inner/TransferCreatorImpl.kt @@ -98,7 +98,7 @@ class TransferCreatorImpl @Autowired constructor( inputMap[CheckoutAtomParam::repositoryType.name] = CheckoutAtomParam.CheckoutRepositoryType.ID } - step.checkout?.url?.startsWith("http") == true -> { + step.checkout?.url != null -> { inputMap[CheckoutAtomParam::repositoryUrl.name] = step.checkout?.url!! inputMap[CheckoutAtomParam::repositoryType.name] = CheckoutAtomParam.CheckoutRepositoryType.URL } diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt index 69c4034ae3d3..c92ba735236a 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/models/Variable.kt @@ -73,7 +73,7 @@ interface IVariable @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) data class Variable( - val value: String?, + val value: Any?, var readonly: Boolean? = false, @JsonProperty("allow-modify-at-startup") val allowModifyAtStartup: Boolean? = true, @@ -178,6 +178,8 @@ enum class VariablePropType(val value: String) { TIME_PICKER("time-picker"), COMPANY_STAFF_INPUT("company-staff-input"), GIT_REF("git-ref"), + SVN_REF("svn-tag"), + REPO_REF("repo-ref"), CODE_LIB("code-lib"), CONTAINER_TYPE("container-type"), ARTIFACTORY("artifactory"), @@ -197,6 +199,7 @@ enum class VariablePropType(val value: String) { ARTIFACTORY -> BuildFormPropertyType.ARTIFACTORY SUB_PIPELINE -> BuildFormPropertyType.SUB_PIPELINE CUSTOM_FILE -> BuildFormPropertyType.CUSTOM_FILE + REPO_REF -> BuildFormPropertyType.REPO_REF else -> BuildFormPropertyType.STRING } diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt index 2985ec5dc44f..0ecbafb960c0 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/v3/parsers/template/YamlObjects.kt @@ -70,33 +70,37 @@ object YamlObjects { } fun getVariable(fromPath: TemplatePath, key: String, variable: Map): Variable { + val props = variable["props"]?.let { + getVarProps(fromPath, it) + } + val type = props?.type val va = Variable( - value = variable["value"]?.toString(), + value = if (type == VariablePropType.REPO_REF.value) { + variable["value"] ?: mapOf() + } else { + variable["value"]?.toString() + }, readonly = getNullValue("readonly", variable)?.toBoolean(), const = getNullValue("const", variable)?.toBoolean(), allowModifyAtStartup = getNullValue("allow-modify-at-startup", variable)?.toBoolean(), - props = if (variable["props"] == null) { - null - } else { - getVarProps(fromPath, variable["props"]!!) - } + props = props ) // 只有列表需要判断 - if (va.props?.type == VariablePropType.SELECTOR.value || va.props?.type == VariablePropType.CHECKBOX.value) { + if (type == VariablePropType.SELECTOR.value || type == VariablePropType.CHECKBOX.value) { // 这期暂不对拉取远程接口的参数做校验 - if (va.props.payload != null) { + if (props.payload != null) { return va } - - if (!va.value.isNullOrBlank() && va.props.options.isNullOrEmpty()) { + val value = va.value as String? + if (!value.isNullOrBlank() && props.options.isNullOrEmpty()) { throw YamlFormatException( - "$fromPath variable $key format error: value ${va.value} not in variable options" + "$fromPath variable $key format error: value $value not in variable options" ) } val expectValues = - va.value?.split(",")?.asSequence()?.filter { it.isNotBlank() }?.map { it.trim() }?.toSet() - val resultValues = va.props.options?.map { it.id.toString() }?.toSet() ?: emptySet() + value?.split(",")?.asSequence()?.filter { it.isNotBlank() }?.map { it.trim() }?.toSet() + val resultValues = props.options?.map { it.id.toString() }?.toSet() ?: emptySet() // 说明默认值没有匹配到选项值,报错 if (expectValues?.subtract(resultValues)?.isEmpty() == false) { throw YamlFormatException( @@ -106,7 +110,7 @@ object YamlObjects { } // 校验bool - if (va.props?.type == VariablePropType.BOOLEAN.value && (va.value != "true" && va.value != "false")) { + if (type == VariablePropType.BOOLEAN.value && (va.value != "true" && va.value != "false")) { throw YamlFormatException( "$fromPath variable $key format error: bool value ${va.value} not true / false" ) diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json b/src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json index 4edadcaff148..83662096e8a1 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/resources/schema/V3_0/ci.json @@ -1427,6 +1427,16 @@ "type" : "number" }, { "type" : "boolean" + }, { + "type" : "object", + "properties" : { + "repo-name" : { + "type" : "string" + }, + "branch" : { + "type" : "string" + } + } } ] }, "readonly" : { @@ -1450,7 +1460,7 @@ "type" : "string" }, "type" : { - "enum" : [ "vuex-input", "vuex-textarea", "selector", "checkbox", "boolean", "time-picker", "company-staff-input", "git-ref", "code-lib", "container-type", "artifactory", "sub-pipeline", "custom-file", "tips" ] + "enum" : [ "vuex-input", "vuex-textarea", "selector", "checkbox", "boolean", "time-picker", "company-staff-input", "git-ref", "code-lib", "container-type", "artifactory", "sub-pipeline", "custom-file", "tips", "repo-ref" ] }, "options" : { "type" : "array", @@ -1469,7 +1479,13 @@ } ] }, "label" : { - "type" : "string" + "anyOf" : [ { + "type" : "string" + }, { + "type" : "number" + }, { + "type" : "boolean" + } ] }, "description" : { "type" : "string" @@ -1583,6 +1599,16 @@ "type" : "number" }, { "type" : "boolean" + }, { + "type" : "object", + "properties" : { + "repo-name" : { + "type" : "string" + }, + "branch" : { + "type" : "string" + } + } } ] }, "readonly" : { @@ -1609,7 +1635,7 @@ "type" : "string" }, "type" : { - "enum" : [ "vuex-input", "vuex-textarea", "selector", "checkbox", "boolean", "time-picker", "company-staff-input", "git-ref", "code-lib", "container-type", "artifactory", "sub-pipeline", "custom-file", "tips" ] + "enum" : [ "vuex-input", "vuex-textarea", "selector", "checkbox", "boolean", "time-picker", "company-staff-input", "git-ref", "code-lib", "container-type", "artifactory", "sub-pipeline", "custom-file", "tips", "repo-ref" ] }, "options" : { "type" : "array", @@ -1628,7 +1654,13 @@ } ] }, "label" : { - "type" : "string" + "anyOf" : [ { + "type" : "string" + }, { + "type" : "number" + }, { + "type" : "boolean" + } ] }, "description" : { "type" : "string" diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/PipelineVersionWithModelRequest.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/PipelineVersionWithModelRequest.kt index 54049f3f171b..4c634c21308c 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/PipelineVersionWithModelRequest.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/PipelineVersionWithModelRequest.kt @@ -37,7 +37,7 @@ data class PipelineVersionWithModelRequest( @get:Schema(title = "草稿的来源版本(前端保存时传递)", required = true) val baseVersion: Int, @get:Schema(title = "流水线模型", required = true) - val modelAndSetting: PipelineModelAndSetting, + val modelAndSetting: PipelineModelAndSetting?, @get:Schema(title = "流水线YAML编排(不为空时以YAML为准)", required = false) val yaml: String?, @get:Schema(title = "存储格式", required = false) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/enums/BuildFormPropertyType.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/enums/BuildFormPropertyType.kt index 856d95b83bad..90215de579dd 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/enums/BuildFormPropertyType.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/enums/BuildFormPropertyType.kt @@ -36,6 +36,7 @@ enum class BuildFormPropertyType(val value: String) { BOOLEAN("boolean"), SVN_TAG("svn_tag"), GIT_REF("git_ref"), + REPO_REF("repo_ref"), MULTIPLE("multiple"), CODE_LIB("code_lib"), CONTAINER_TYPE("container_type"), // 构建机类型(公共构建机,第三方构建机,PCG构建机等) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt index a2753a79cc05..4a072234b2f9 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/BuildFormProperty.kt @@ -29,6 +29,7 @@ package com.tencent.devops.common.pipeline.pojo import com.tencent.devops.common.api.enums.ScmType import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType +import com.tencent.devops.common.pipeline.pojo.cascade.BuildCascadeProps import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "构建模型-表单元素属性") @@ -89,5 +90,7 @@ data class BuildFormProperty( @get:Schema(title = "参数值是否必填", required = false) val valueNotEmpty: Boolean? = false, @get:Schema(title = "页面所需内容,后台仅保存,不做处理", required = false) - val payload: Any? = null + val payload: Any? = null, + @get:Schema(title = "级联选择器属性", required = false) + var cascadeProps: BuildCascadeProps? = null ) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/TemplateInstanceCreateRequest.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/TemplateInstanceCreateRequest.kt index 9d9e8948e1d9..366e8d86b04f 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/TemplateInstanceCreateRequest.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/TemplateInstanceCreateRequest.kt @@ -52,5 +52,7 @@ data class TemplateInstanceCreateRequest( @get:Schema(title = "是否继承项目流水线语言风格", required = false) var inheritedDialect: Boolean? = true, @get:Schema(title = "流水线语言风格", required = false) - var pipelineDialect: String? = null + var pipelineDialect: String? = null, + @get:Schema(title = "流水线标签", required = false) + val labels: List = emptyList() ) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/BuildCascadeProps.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/BuildCascadeProps.kt new file mode 100644 index 000000000000..6c71c179d0f8 --- /dev/null +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/BuildCascadeProps.kt @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.common.pipeline.pojo.cascade + +import com.tencent.devops.common.pipeline.pojo.BuildFormValue +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "构建模型-表单元素属性") +data class BuildCascadeProps( + // 级联ID + val id: String, + // 级联下拉框值 + val options: List, + // 后端搜索url + val searchUrl: String?, + // 搜索key + val replaceKey: String?, + // 级联子级 + var children: BuildCascadeProps? = null +) diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/CascadeParam.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/CascadeParam.kt new file mode 100644 index 000000000000..8cb0e3002439 --- /dev/null +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/CascadeParam.kt @@ -0,0 +1,56 @@ +package com.tencent.devops.common.pipeline.pojo.cascade + +import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType +import com.tencent.devops.common.pipeline.pojo.BuildFormProperty +import com.tencent.devops.common.pipeline.utils.CascadePropertyUtils +import org.slf4j.LoggerFactory + +abstract class CascadeParam constructor( + open val type: BuildFormPropertyType, + open val chain: List +) { + fun getProps(prop: BuildFormProperty, projectId: String): BuildCascadeProps { + if (chain.size < 2 || chain.size != chainHandler().size) { + // 最少两个链路节点,且节点数和链式处理器数量相等 + throw IllegalArgumentException("chain size must be 2 and equal to chainHandler size") + } + val defaultValue = getDefaultValue(prop) + val map = chain.associateBy({ it }) { + val propsHandler = + chainHandler()[it] ?: throw IllegalArgumentException("can not find handler for $it|$type") + propsHandler.handle( + key = it, + defaultValue = defaultValue[it] ?: "", + projectId = projectId + ) + } + // 链式关系处理 + for (i in chain.size - 1 downTo 1) { + map[chain[i - 1]]?.children = map[chain[i]] + } + return map[chain.first()]!! + } + + private fun getDefaultValue(prop: BuildFormProperty): Map { + val defaultValue = CascadePropertyUtils.parseDefaultValue( + key = prop.id, + defaultValue = prop.defaultValue, + type = prop.type + ) + return if (!chain.find { !defaultValue.containsKey(it) }.isNullOrBlank()) { + mapOf() + } else { + defaultValue + } + } + + abstract fun chainHandler(): Map + + companion object { + val logger = LoggerFactory.getLogger(CascadeParam::class.java) + } +} + +interface CascadeParamPropsHandler { + fun handle(key: String, defaultValue: String, projectId: String): BuildCascadeProps +} diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/RepoRefCascadeParam.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/RepoRefCascadeParam.kt new file mode 100644 index 000000000000..bf55f92ac448 --- /dev/null +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/cascade/RepoRefCascadeParam.kt @@ -0,0 +1,63 @@ +package com.tencent.devops.common.pipeline.pojo.cascade + +import com.tencent.devops.common.api.enums.ScmType +import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType +import com.tencent.devops.common.pipeline.pojo.BuildFormValue + +class RepoRefCascadeParam : CascadeParam( + type = BuildFormPropertyType.REPO_REF, + chain = listOf(SELECTOR_KEY_REPO_NAME, SELECTOR_KEY_BRANCH) +) { + override fun chainHandler(): Map { + return mapOf( + SELECTOR_KEY_REPO_NAME to repoNameHandler, + SELECTOR_KEY_BRANCH to branchHandler + ) + } + + private val repoNameHandler = object : CascadeParamPropsHandler { + override fun handle(key: String, defaultValue: String, projectId: String): BuildCascadeProps { + val repositoryTypes = SUPPORT_REPO_TYPE.joinToString(separator = ",") { it.name } + return BuildCascadeProps( + id = key, + options = listOf(BuildFormValue(defaultValue, defaultValue)), + searchUrl = "process/api/user/buildParam/repository/$projectId/aliasName?aliasName={words}&" + + "permission=LIST&repositoryType=$repositoryTypes", + replaceKey = "{words}" + ) + } + } + + private val branchHandler = object : CascadeParamPropsHandler { + override fun handle(key: String, defaultValue: String, projectId: String) = + BuildCascadeProps( + id = key, + options = listOf(BuildFormValue(defaultValue, defaultValue)), + searchUrl = "/process/api/user/buildParam/$projectId/repository/refs?search={branch}&" + + "repositoryType=NAME&repositoryId={parentValue}", + replaceKey = "{branch}" + ) + } + + companion object { + const val SELECTOR_KEY_REPO_NAME = "repo-name" + const val SELECTOR_KEY_BRANCH = "branch" + private val SUPPORT_REPO_TYPE = listOf( + ScmType.CODE_GIT, + ScmType.GITHUB, + ScmType.CODE_SVN, + ScmType.CODE_TGIT, + ScmType.CODE_GITLAB + ) + + fun variableKeyMap(key: String) = mapOf( + SELECTOR_KEY_REPO_NAME to "$key.$SELECTOR_KEY_REPO_NAME", + SELECTOR_KEY_BRANCH to "$key.$SELECTOR_KEY_BRANCH" + ) + + fun defaultValue() = mapOf( + SELECTOR_KEY_REPO_NAME to "", + SELECTOR_KEY_BRANCH to "" + ) + } +} diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/setting/PipelineSetting.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/setting/PipelineSetting.kt index 41c077ce668f..57b9d5eba8ac 100644 --- a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/setting/PipelineSetting.kt +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/pojo/setting/PipelineSetting.kt @@ -30,6 +30,7 @@ package com.tencent.devops.common.pipeline.pojo.setting import com.tencent.devops.common.api.pojo.PipelineAsCodeSettings import com.tencent.devops.common.pipeline.utils.PIPELINE_RES_NUM_MIN import com.tencent.devops.common.pipeline.utils.PIPELINE_SETTING_CONCURRENCY_GROUP_DEFAULT +import com.tencent.devops.common.pipeline.utils.PIPELINE_SETTING_MAX_CON_QUEUE_SIZE_MAX import com.tencent.devops.common.pipeline.utils.PIPELINE_SETTING_MAX_QUEUE_SIZE_DEFAULT import com.tencent.devops.common.pipeline.utils.PIPELINE_SETTING_WAIT_QUEUE_TIME_MINUTE_DEFAULT import com.tencent.devops.common.web.annotation.BkField @@ -165,4 +166,17 @@ data class PipelineSetting( } failSubscription = failSubscriptionList!!.firstOrNull() } + + fun copySubscriptionSettings(other: PipelineSetting) { + successSubscription = other.successSubscription + successSubscriptionList = other.successSubscriptionList + failSubscription = other.failSubscription + failSubscriptionList = other.failSubscriptionList + } + + fun copyConcurrencyGroup(other: PipelineSetting) { + concurrencyGroup = other.concurrencyGroup + concurrencyCancelInProgress = other.concurrencyCancelInProgress + maxConRunningQueueSize = PIPELINE_SETTING_MAX_CON_QUEUE_SIZE_MAX + } } diff --git a/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/CascadePropertyUtils.kt b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/CascadePropertyUtils.kt new file mode 100644 index 000000000000..21254a50b2fc --- /dev/null +++ b/src/backend/ci/core/common/common-pipeline/src/main/kotlin/com/tencent/devops/common/pipeline/utils/CascadePropertyUtils.kt @@ -0,0 +1,40 @@ +package com.tencent.devops.common.pipeline.utils + +import com.fasterxml.jackson.core.type.TypeReference +import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType +import com.tencent.devops.common.pipeline.pojo.cascade.RepoRefCascadeParam +import org.slf4j.LoggerFactory + +object CascadePropertyUtils { + fun getCascadeVariableKeyMap(key: String, type: BuildFormPropertyType) = when (type) { + BuildFormPropertyType.REPO_REF -> RepoRefCascadeParam.variableKeyMap(key) + else -> mapOf() + } + + /** + * 解析级联选择器默认值,当解析默认值失败时,使用默认值 + */ + fun parseDefaultValue(key: String, defaultValue: Any, type: BuildFormPropertyType?) = try { + if (defaultValue is String) { + JsonUtil.to( + json = defaultValue, + typeReference = object : TypeReference>() {} + ) + } else { + defaultValue as Map + } + } catch (ignored: Exception) { + logger.warn("parse repo ref error, key: $key, defaultValue: $defaultValue") + getDefaultValue(type) + } + + private fun getDefaultValue(type: BuildFormPropertyType?) = when (type) { + BuildFormPropertyType.REPO_REF -> RepoRefCascadeParam.defaultValue() + else -> mapOf() + } + + fun supportCascadeParam(type: BuildFormPropertyType?) = type == BuildFormPropertyType.REPO_REF + + val logger = LoggerFactory.getLogger(CascadePropertyUtils::class.java) +} \ No newline at end of file diff --git a/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/code/svn/api/SVNApi.kt b/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/code/svn/api/SVNApi.kt index bacd592c4030..433296446542 100644 --- a/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/code/svn/api/SVNApi.kt +++ b/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/code/svn/api/SVNApi.kt @@ -110,6 +110,7 @@ open class SVNApi { private fun getBody(request: Request): String { OkhttpUtils.doHttp(request).use { response -> if (!response.isSuccessful) { + logger.warn("fail to get response|url=${request.url}|code=${response.code}|body=${response.body}") when { response.code == 401 -> throw ScmException( I18nUtil.getCodeLanMessage(CommonMessageCode.ENGINEERING_REPO_UNAUTHORIZED), diff --git a/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/pojo/GitRepositoryResp.kt b/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/pojo/GitRepositoryResp.kt index 816fe961c53b..f11138e3f7bc 100644 --- a/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/pojo/GitRepositoryResp.kt +++ b/src/backend/ci/core/common/common-scm/src/main/kotlin/com/tencent/devops/scm/pojo/GitRepositoryResp.kt @@ -31,6 +31,8 @@ import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "创建git仓库响应体") class GitRepositoryResp( + @get:Schema(title = "仓库ID", required = true) + val id: Long, @get:Schema(title = "仓库名称", required = true) val name: String, @get:Schema(title = "仓库地址", required = true) diff --git a/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties b/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties index 33291f28133f..7da63d49dcca 100644 --- a/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties +++ b/src/backend/ci/core/common/common-service/src/main/resources/common-service.properties @@ -29,11 +29,5 @@ spring.jmx.enabled=true management.endpoints.web.exposure.include=* management.health.elasticsearch.enabled=false management.endpoint.health.show-details=ALWAYS -management.security.enabled=false management.endpoints.web.base-path=/management -management.metrics.tags.application=${spring.application.name} -spring.cloud.consul.discovery.health-check-path=/management/health -spring.cloud.consul.discovery.heartbeat.enabled=true -spring.cloud.consul.discovery.query-passing=true -spring.cloud.consul.discovery.preferIpAddress=true -spring.cloud.kubernetes.discovery.all-namespaces=true +management.metrics.tags.application=${spring.application.name} \ No newline at end of file diff --git a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/filter/ThirdFilter.kt b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/filter/ThirdFilter.kt index 14bc0d3162ea..0ebc5f5d15dc 100644 --- a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/filter/ThirdFilter.kt +++ b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/filter/ThirdFilter.kt @@ -25,11 +25,13 @@ class ThirdFilter( private val thirdSecretToken: String? = null, private val gitScmService: GitScmService, private val callbackCircuitBreakerRegistry: CircuitBreakerRegistry?, - private val failedReason: String = "" + private val failedReason: String = "", + private val eventType: String ) : WebhookFilter { companion object { private const val FILTER_TOKEN_HEADER = "X-DEVOPS-FILTER-TOKEN" + private const val FILTER_EVENT_TYPE_HEADER = "X-DEVOPS-EVENT-TYPE" private const val MAX_RETRY_COUNT = 3 private val logger = LoggerFactory.getLogger(ThirdFilter::class.java) } @@ -63,7 +65,8 @@ class ThirdFilter( projectId = projectId, pipelineId = pipelineId, event = event, - changeFiles = changeFiles + changeFiles = changeFiles, + eventType = eventType ) ) val builder = Request.Builder() @@ -77,6 +80,7 @@ class ThirdFilter( credentialId = thirdSecretToken ) builder.addHeader(FILTER_TOKEN_HEADER, thirdSecretTokenValue) + builder.addHeader(FILTER_EVENT_TYPE_HEADER, eventType) } return HttpRetryUtils.retry(MAX_RETRY_COUNT) { OkhttpUtils.doShortHttp(request = builder.build()).use { response -> diff --git a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitMrTriggerHandler.kt b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitMrTriggerHandler.kt index 0e5bfcc311e1..073a4fbcfdaf 100644 --- a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitMrTriggerHandler.kt +++ b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitMrTriggerHandler.kt @@ -318,7 +318,8 @@ class TGitMrTriggerHandler( thirdSecretToken = thirdSecretToken, gitScmService = gitScmService, callbackCircuitBreakerRegistry = callbackCircuitBreakerRegistry, - failedReason = I18Variable(code = WebhookI18nConstants.THIRD_FILTER_NOT_MATCH).toJsonStr() + failedReason = I18Variable(code = WebhookI18nConstants.THIRD_FILTER_NOT_MATCH).toJsonStr(), + eventType = getEventType().name ) return listOf( wipFilter, userFilter, targetBranchFilter, diff --git a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitPushTriggerHandler.kt b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitPushTriggerHandler.kt index bb83b1716a44..bae6098f5d60 100644 --- a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitPushTriggerHandler.kt +++ b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/handler/tgit/TGitPushTriggerHandler.kt @@ -141,9 +141,9 @@ class TGitPushTriggerHandler( override fun getAction(event: GitPushEvent): String? { return when { - event.action_kind.isNullOrBlank() -> event.action_kind - event.before == EMPTY_COMMIT_ID -> TGitPushActionType.NEW_BRANCH.value - else -> TGitPushActionType.PUSH_FILE.value + !event.action_kind.isNullOrBlank() -> event.action_kind + event.before == EMPTY_COMMIT_ID -> TGitPushActionKind.CREATE_BRANCH.value + else -> TGitPushActionKind.CLIENT_PUSH.value } } @@ -166,8 +166,9 @@ class TGitPushTriggerHandler( override fun preMatch(event: GitPushEvent): WebhookMatchResult { val isMatch = when { event.total_commits_count <= 0 -> { - logger.info("Git web hook no commit(${event.total_commits_count})") - false + val operationKind = event.operation_kind + logger.info("Git web hook no commit(${event.total_commits_count})|operationKind=$operationKind") + operationKind == TGitPushOperationKind.UPDATE_NONFASTFORWORD.value } GitUtils.isPrePushBranch(event.ref) -> { logger.info("Git web hook is pre-push event|branchName=${event.ref}") @@ -220,13 +221,13 @@ class TGitPushTriggerHandler( val skipCiFilter = KeywordSkipFilter( pipelineId = pipelineId, keyWord = KEYWORD_SKIP_CI, - triggerOnMessage = event.commits?.get(0)?.message ?: "" + triggerOnMessage = event.commits?.firstOrNull()?.message ?: "" ) val commits = event.commits val commitMessageFilter = CommitMessageFilter( includeCommitMsg, excludeCommitMsg, - commits?.first()?.message ?: "", + commits?.firstOrNull()?.message ?: "", pipelineId ) val eventPaths = if (tryGetChangeFilePath(this, event.operation_kind)) { @@ -284,7 +285,8 @@ class TGitPushTriggerHandler( thirdSecretToken = thirdSecretToken, gitScmService = gitScmService, callbackCircuitBreakerRegistry = callbackCircuitBreakerRegistry, - failedReason = I18Variable(code = WebhookI18nConstants.THIRD_FILTER_NOT_MATCH).toJsonStr() + failedReason = I18Variable(code = WebhookI18nConstants.THIRD_FILTER_NOT_MATCH).toJsonStr(), + eventType = getEventType().name ) return listOf( userFilter, branchFilter, skipCiFilter, diff --git a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/pojo/ThirdFilterBody.kt b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/pojo/ThirdFilterBody.kt index 696b51106da0..0b5d62d3b070 100644 --- a/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/pojo/ThirdFilterBody.kt +++ b/src/backend/ci/core/common/common-webhook/biz-common-webhook/src/main/kotlin/com/tencent/devops/common/webhook/service/code/pojo/ThirdFilterBody.kt @@ -6,5 +6,6 @@ data class ThirdFilterBody( val projectId: String, val pipelineId: String, val event: CodeWebhookEvent, - val changeFiles: Set? = emptySet() + val changeFiles: Set? = emptySet(), + val eventType: String ) diff --git a/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/constant/EnvironmentMessageCode.kt b/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/constant/EnvironmentMessageCode.kt index 7c47df98395c..e7f1ef7dd354 100644 --- a/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/constant/EnvironmentMessageCode.kt +++ b/src/backend/ci/core/environment/api-environment/src/main/kotlin/com/tencent/devops/environment/constant/EnvironmentMessageCode.kt @@ -104,6 +104,7 @@ object EnvironmentMessageCode { const val ERROR_JOB_INSTANCE_NOT_BELONG_TO_PROJECT = "2105054" // 环境管理: 请求的job实例不属于当前项目或已过期(超过一个月) const val ERROR_FAIL_TO_CREATE_AGENT_INSTALL_TASK = "2105055" // 环境管理: 创建Agent安装任务失败:{0} const val ERROR_INPUT_TOO_MANY_IP = "2105056" // 环境管理: 输入的IP数量不可超过{0} + const val ERROR_NODE_NOT_BELONG_TO_PROJECT = "2105057" // 环境管理: IP {0} 未被作为节点导入项目 {1},请到【环境管理-节点】导入测试机后重试 const val BK_NORMAL_VERSION = "bkNormalVersion" // 8核16G(普通版) const val BK_INTEL_XEON_SKYLAKE_PROCESSOR = "bkIntelXeonSkylakeProcessor" // 2.5GHz 64核 Intel Xeon Skylake 6133处理器 diff --git a/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/utils/BuildLogPrinter.kt b/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/utils/BuildLogPrinter.kt index 24c2c06fc809..815bc7220777 100644 --- a/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/utils/BuildLogPrinter.kt +++ b/src/backend/ci/core/log/api-log/src/main/kotlin/com/tencent/devops/common/log/utils/BuildLogPrinter.kt @@ -237,7 +237,7 @@ class BuildLogPrinter( tag = tag, subTag = subTag, containerHashId = containerHashId, - logType = LogType.DEBUG, + logType = LogType.WARN, executeCount = executeCount, jobId = jobId, stepId = stepId diff --git a/src/backend/ci/core/misc/api-gpt/src/main/kotlin/com/tencent/devops/gpt/constant/GptMessageCode.kt b/src/backend/ci/core/misc/api-gpt/src/main/kotlin/com/tencent/devops/gpt/constant/GptMessageCode.kt index 0940f20c90c7..5236981045ce 100644 --- a/src/backend/ci/core/misc/api-gpt/src/main/kotlin/com/tencent/devops/gpt/constant/GptMessageCode.kt +++ b/src/backend/ci/core/misc/api-gpt/src/main/kotlin/com/tencent/devops/gpt/constant/GptMessageCode.kt @@ -61,6 +61,9 @@ object GptMessageCode { // 发生错误!插件日志未入库或已清理。 const val SCRIPT_ERROR_ANALYSIS_CHAT_TASK_LOGS_EMPTY = "scriptErrorAnalysisChatTaskLogsEmpty" + // 发生错误!请刷新页面后重试。 + const val SCRIPT_ERROR_ANALYSIS_CHAT_TASK_UNDEFINED = "scriptErrorAnalysisChatTaskUndefined" + // 当前模型忙,请稍后重试 const val GPT_BUSY = "gptBusy" diff --git a/src/backend/ci/core/misc/biz-gpt/src/main/kotlin/com/tencent/devops/gpt/service/LLMService.kt b/src/backend/ci/core/misc/biz-gpt/src/main/kotlin/com/tencent/devops/gpt/service/LLMService.kt index 066643e98010..7f36bb791d35 100644 --- a/src/backend/ci/core/misc/biz-gpt/src/main/kotlin/com/tencent/devops/gpt/service/LLMService.kt +++ b/src/backend/ci/core/misc/biz-gpt/src/main/kotlin/com/tencent/devops/gpt/service/LLMService.kt @@ -40,6 +40,7 @@ import com.tencent.devops.gpt.constant.GptMessageCode.GPT_BUSY import com.tencent.devops.gpt.constant.GptMessageCode.SCRIPT_ERROR_ANALYSIS_CHAT_TASK_LOGS_EMPTY import com.tencent.devops.gpt.constant.GptMessageCode.SCRIPT_ERROR_ANALYSIS_CHAT_TASK_NOT_FAILED import com.tencent.devops.gpt.constant.GptMessageCode.SCRIPT_ERROR_ANALYSIS_CHAT_TASK_NOT_FIND +import com.tencent.devops.gpt.constant.GptMessageCode.SCRIPT_ERROR_ANALYSIS_CHAT_TASK_UNDEFINED import com.tencent.devops.gpt.dao.AIScoreDao import com.tencent.devops.gpt.pojo.AIScoreRes import com.tencent.devops.gpt.service.config.GptGatewayCondition @@ -91,16 +92,20 @@ class LLMService @Autowired constructor( refresh: Boolean?, output: ChunkedOutput ) { + if (taskId == "undefined") { + output.write(I18nUtil.getCodeLanMessage(SCRIPT_ERROR_ANALYSIS_CHAT_TASK_UNDEFINED)) + return + } logger.info("scriptErrorAnalysisChat|$userId|$projectId|$pipelineId|$buildId|$taskId|$executeCount|$refresh") // 拿插件执行信息 val task = client.get(ServicePipelineTaskResource::class).getTaskBuildDetail( - projectId = projectId, buildId = buildId, taskId = taskId, stepId = null, executeCount = executeCount + projectId = projectId, buildId = buildId, taskId = taskId, stepId = null, executeCount = null ).data ?: run { output.write(I18nUtil.getCodeLanMessage(SCRIPT_ERROR_ANALYSIS_CHAT_TASK_NOT_FIND)) return } // 校验插件状态 - if (task.status != BuildStatus.FAILED) { + if (task.status != BuildStatus.FAILED && task.executeCount == executeCount) { output.write(I18nUtil.getCodeLanMessage(SCRIPT_ERROR_ANALYSIS_CHAT_TASK_NOT_FAILED)) return } diff --git a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt index f3c5e460f907..625015390874 100644 --- a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt +++ b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/service/NotifyMessageTemplateServiceImpl.kt @@ -39,6 +39,7 @@ import com.tencent.devops.common.notify.enums.NotifyType import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.service.config.CommonConfig +import com.tencent.devops.common.service.trace.TraceTag import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.model.notify.tables.records.TCommonNotifyMessageTemplateRecord @@ -63,6 +64,7 @@ import com.tencent.devops.notify.service.notifier.NotifierUtils import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory +import org.slf4j.MDC import org.springframework.beans.factory.annotation.Autowired import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service @@ -94,7 +96,9 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( expiredTimeInSeconds = 60 ) + val traceId = MDC.get(TraceTag.BIZID) Executors.newFixedThreadPool(1).submit { + MDC.put(TraceTag.BIZID, traceId) if (redisLock.tryLock()) { try { logger.info("start init MessageTemplate") @@ -117,6 +121,7 @@ class NotifyMessageTemplateServiceImpl @Autowired constructor( val yamlStr = inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() } val templates = YamlUtil.to(yamlStr, object : TypeReference>() {}) templates.forEach { template -> + logger.info("update message template:$template") val tCommonNotifyMessageTemplateRecord = TCommonNotifyMessageTemplateRecord() tCommonNotifyMessageTemplateRecord.id = template.id tCommonNotifyMessageTemplateRecord.templateCode = template.templateCode diff --git a/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt new file mode 100644 index 000000000000..af16ca2cd198 --- /dev/null +++ b/src/backend/ci/core/openapi/api-openapi/src/main/kotlin/com/tencent/devops/openapi/api/apigw/v4/ApigwRepositoryOauthResourceV4.kt @@ -0,0 +1,72 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ +package com.tencent.devops.openapi.api.apigw.v4 + +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.pojo.Result +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "OPEN_API_REPOSITORY_V4", description = "OPEN-API-代码库OAUTH授权") +@Path("/{apigwType:apigw-user|apigw-app|apigw}/v4/repositories/oauth") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@SuppressWarnings("All") +interface ApigwRepositoryOauthResourceV4 { + @Operation( + summary = "校验用户是否已经OAUTH授权", + tags = ["v4_app_oauth_isOauth", "v4_user_oauth_isOauth"] + ) + @GET + @Path("/isOauth") + fun isOauth( + @Parameter(description = "appCode", required = true, example = AUTH_HEADER_DEVOPS_APP_CODE_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_APP_CODE) + appCode: String?, + @Parameter(description = "apigw Type", required = true) + @PathParam("apigwType") + apigwType: String?, + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_DEVOPS_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_DEVOPS_USER_ID) + userId: String, + @Parameter(description = "代码库类型", required = true) + @QueryParam("scmCode") + scmCode: String + ): Result +} diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt new file mode 100644 index 000000000000..77b7f636ada5 --- /dev/null +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/resources/apigw/v4/ApigwOauthResourceV4Impl.kt @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ +package com.tencent.devops.openapi.resources.apigw.v4 + +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.client.Client +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.openapi.api.apigw.v4.ApigwRepositoryOauthResourceV4 +import com.tencent.devops.repository.api.ServiceOauthResource +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class ApigwOauthResourceV4Impl @Autowired constructor(private val client: Client) : ApigwRepositoryOauthResourceV4 { + override fun isOauth( + appCode: String?, + apigwType: String?, + userId: String, + scmCode: String + ): Result { + logger.info("OPENAPI_OAUTH_V4|$userId|verify if $scmCode oauth authorization has been performed") + val result = when (scmCode) { + ScmCode.TGIT.name -> { + client.get(ServiceOauthResource::class).isOAuth( + userId = userId, + redirectUrl = null, + redirectUrlType = null + ).data?.status + } + + ScmCode.GITHUB.name -> { + client.get(ServiceOauthResource::class).githubOAuth( + userId = userId + ).data?.status + } + + else -> { + null + } + } + return Result(result == AUTHORIZED_STATUS) + } + + companion object { + private val logger = LoggerFactory.getLogger(ApigwOauthResourceV4Impl::class.java) + private const val AUTHORIZED_STATUS = 200 + } +} diff --git a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt index b42ae3d808c9..3bb0fa292502 100644 --- a/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt +++ b/src/backend/ci/core/openapi/biz-openapi/src/main/kotlin/com/tencent/devops/openapi/service/doc/DocumentService.kt @@ -33,10 +33,8 @@ import com.tencent.devops.common.api.util.FileUtil import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.MessageUtil import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_ALL_MODEL_DATA -import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_APPLICATION_STATE_REQUIRED import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_BODY_PARAMETER import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_CURL_PROMPT -import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_DEFAULT_VALUE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_DISCRIMINATOR_ILLUSTRATE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_ERROR_PROMPT import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_HAVE_TO @@ -45,7 +43,6 @@ import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_HTTP_CODE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_ILLUSTRATE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_INPUT_PARAMETER_DESCRIPTION import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_MUST_BE -import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_NO import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_OBJECT_PROPERTY_ILLUSTRATE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_PARAM_ILLUSTRATE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_PARAM_NAME @@ -62,7 +59,6 @@ import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_RESPONSE_PARAME import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_RETURNS_THE_SAMPLE import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_THE_FIELD_IS_READ_ONLY import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_USER_NAME -import com.tencent.devops.openapi.constant.OpenAPIMessageCode.BK_YES import com.tencent.devops.openapi.pojo.SwaggerDocParameterInfo import com.tencent.devops.openapi.pojo.SwaggerDocResponse import com.tencent.devops.openapi.utils.markdown.Code @@ -85,13 +81,18 @@ import io.swagger.v3.oas.models.media.StringSchema import io.swagger.v3.oas.models.parameters.Parameter import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.responses.ApiResponse +import java.lang.reflect.Modifier +import java.math.BigDecimal +import java.time.LocalDateTime import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.reflect.KFunction import kotlin.reflect.KType +import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaType +import kotlin.reflect.typeOf import org.apache.commons.lang3.StringUtils import org.reflections.Reflections import org.springframework.beans.factory.annotation.Value @@ -160,8 +161,7 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), rows = parseParameters( operation.parameters?.filter { it.`in` == PATH_PARAM } ?: emptyList() @@ -178,8 +178,7 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), rows = parseParameters( operation.parameters?.filter { it.`in` == QUERY_PARAM } ?: emptyList() @@ -196,8 +195,7 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), rows = parseParameters( operation.parameters?.filter { it.`in` == HEADER_PARAM } ?: emptyList() @@ -207,11 +205,10 @@ class DocumentService { .setRow( AUTH_HEADER_USER_ID, "string", - getI18n(BK_APPLICATION_STATE_REQUIRED), - getI18n(BK_USER_NAME), - "{X-DEVOPS-UID}" + "√", + getI18n(BK_USER_NAME) ) - .setRow("Content-Type", "string", getI18n(BK_YES), "", "application/json") + .setRow("Content-Type", "string", "√", "application/json") .removeRow(AUTH_HEADER_DEVOPS_APP_CODE) }, path + httpMethod + "header") ) @@ -276,21 +273,23 @@ class DocumentService { // 组装所有已使用的模型 loadMarkdown.addAll(parseAllModel(onLoadModel, loadedModel)) operation.tags.forEach { tag -> - response[tag] = SwaggerDocResponse( + val res = SwaggerDocResponse( path = path, httpMethod = httpMethod.name, markdown = if (checkMDData) loadMarkdown.joinToString(separator = "") else null, metaData = if (checkMetaData) loadMarkdown else null ) + response[tag] = res if (!outputPath.isNullOrBlank()) { FileUtil.outFile(outputPath, "$tag.md", loadMarkdown.joinToString(separator = "")) } + + if (!outputPath.isNullOrBlank()) { + FileUtil.outFile("$outputPath/json", "$tag.json", JsonUtil.toJson(res)) + } } } } - if (!outputPath.isNullOrBlank()) { - FileUtil.outFile(outputPath, "all.json", JsonUtil.toJson(response)) - } onLoadTable.clear() definitions.clear() return response @@ -315,8 +314,7 @@ class DocumentService { val reflectInfo = parametersInfo?.get("${model.title}@${model.name}")?.get(table.columns[0]) if (reflectInfo != null) { val column = table.columns.toMutableList() - column[2] = if (reflectInfo.markedNullable.not()) getI18n(BK_YES) else getI18n(BK_NO) - column[4] = if (reflectInfo.markedNullable) reflectInfo.defaultValue ?: "" else column[4] + column[2] = if (reflectInfo.markedNullable.not()) "√" else "" table.columns = column } } @@ -325,10 +323,9 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), - rows = tableRows, + rows = tableRows.sortedBy { it.columns[0] }, key = "model_$key" ) }, key) @@ -352,20 +349,18 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), - rows = tableRows, + rows = tableRows.sortedBy { it.columns[0] }, key = "model_$it" ) }, it).apply { if (it in polymorphismMap) { this.setRow( - (definitions[it] as Schema).discriminator.toString(), + (definitions[it] as Schema).discriminator.propertyName, "string", - getI18n(BK_YES), - getI18n(BK_DISCRIMINATOR_ILLUSTRATE, arrayOf("${polymorphismMap[it]?.keys}")), - "" + "√", + getI18n(BK_DISCRIMINATOR_ILLUSTRATE, arrayOf("${polymorphismMap[it]?.keys}")) ) } }.checkLoadModel(onLoadModel) @@ -376,7 +371,7 @@ class DocumentService { // 多态类展示 polymorphismMap[it]?.forEach { (child, value) -> - val discriminator = (definitions[it] as Schema).discriminator.toString() + val discriminator = (definitions[it] as Schema).discriminator.propertyName val childModel = cacheOrLoad({ null }, child) .setRow( discriminator, @@ -417,7 +412,7 @@ class DocumentService { ) ) markdownElement.add( - Code(language = "Json", body = JsonUtil.toJson(loadJson), key = "${httpStatus}_return_example") + Code(language = "Json", body = JsonUtil.toSortJson(loadJson), key = "${httpStatus}_return_example") ) } return markdownElement @@ -444,7 +439,7 @@ class DocumentService { ) ) val jsonString = try { - JsonUtil.toJson(jsonSimple) + JsonUtil.toSortJson(jsonSimple) } catch (e: Throwable) { jsonSimple.toString() } @@ -493,7 +488,7 @@ class DocumentService { body = "$httpMethod ${getI18n(BK_REQUEST_SAMPLE)}", key = "${httpMethod}_request_sample_title" ), - Code(language = "Json", body = JsonUtil.toJson(outJson), key = "${httpMethod}_request_sample") + Code(language = "Json", body = JsonUtil.toSortJson(outJson), key = "${httpMethod}_request_sample") ) } @@ -502,7 +497,7 @@ class DocumentService { "${it.columns[0]}={${it.columns[0]}}" } ?: "" val headerString = header.rows.takeIf { it.isNotEmpty() }?.joinToString(prefix = "\\\n", separator = "\\\n") { - "-H '${it.columns[0]}: ${it.columns[4]}' " + "-H '${it.columns[0]}: ${it.columns[3]}' " } ?: "" return "curl -X ${httpMethod.toUpperCase()} '${getI18n(BK_CURL_PROMPT, arrayOf(queryString))}' $headerString" } @@ -515,7 +510,7 @@ class DocumentService { TableRow(httpStatus, loadModelType(schema), response.description) ) } - return tableRow + return tableRow.sortedBy { it.columns[0] } } private fun parseRequestBody(requestBody: RequestBody?): List { @@ -541,9 +536,8 @@ class DocumentService { TableRow( it.name, Link(key, "#$key").toString(), - if (it.required == true) getI18n(BK_YES) else getI18n(BK_NO), - it.description, - "" + if (it.required == true) "√" else "", + it.description ) ) } else { @@ -551,14 +545,13 @@ class DocumentService { TableRow( it.name, loadSerializableParameter(it.schema), - if (it.required == true) getI18n(BK_YES) else getI18n(BK_NO), - it.description, - it.schema.default?.toString() ?: "" + if (it.required == true) "√" else "", + it.description ) ) } } - return tableRow + return tableRow.sortedBy { it.columns[0] } } private fun loadSwagger(): OpenAPI { @@ -618,10 +611,9 @@ class DocumentService { getI18n(BK_PARAM_NAME), getI18n(BK_PARAM_TYPE), getI18n(BK_HAVE_TO), - getI18n(BK_PARAM_ILLUSTRATE), - getI18n(BK_DEFAULT_VALUE) + getI18n(BK_PARAM_ILLUSTRATE) ), - rows = table, + rows = table.sortedBy { it.columns[0] }, key = "model_$key" ) }, key @@ -641,9 +633,8 @@ class DocumentService { TableRow( key, loadPropertyType(property), - if (model.required != null && key in model.required) getI18n(BK_YES) else getI18n(BK_NO), - loadDescriptionInfo(property), - loadPropertyDefault(property) + if (model.required != null && key in model.required) "√" else "", + loadDescriptionInfo(property) ) ) } @@ -701,7 +692,7 @@ class DocumentService { else -> { if (model.discriminator != null) { - loadJson[model.discriminator.toString()] = "string" + loadJson[model.discriminator.propertyName] = "string" } model.properties?.forEach { (key, property) -> loadJson[key] = loadPropertyJson(property, deep) @@ -717,6 +708,7 @@ class DocumentService { val loadJson = mutableMapOf() val key = property.`$ref`.removePrefix("#/components/schemas/") definitions[key]?.let { loadModelJson(it, loadJson, deep) } + deep.remove(property.`$ref`) return loadJson } else { return property.`$ref` @@ -725,7 +717,7 @@ class DocumentService { return when (property) { // swagger无法获取到map的key类型 is MapSchema -> { - mapOf("string" to property.additionalProperties) + mapOf("string" to loadPropertyJson(property.additionalProperties as Schema<*>, deep)) } is ObjectSchema -> { @@ -738,7 +730,7 @@ class DocumentService { is StringSchema -> { if (property.enum == null) { - property.type + "" } else { "enum" } @@ -746,11 +738,8 @@ class DocumentService { is BooleanSchema -> false else -> { - val result = when (property.default) { - is Int -> 0 - is Long -> 0L - is Double -> 0.0 - is Float -> 0f + val result = when (property.type) { + "integer" -> 0 else -> property.type } result @@ -841,9 +830,10 @@ class DocumentService { val infoMap = mutableMapOf() val subTypes = it.getAnnotation(JsonSubTypes::class.java).value // val typeInfo = it.getAnnotation(JsonTypeInfo::class.java).property - val name = it.getAnnotation(SchemaAnnotation::class.java)?.name ?: it.name.split(".").last() + val name = it.getAnnotation(SchemaAnnotation::class.java)?.name?.ifBlank { null } + ?: it.name.split(".").last() subTypes.forEach { child -> - val childName = child.value.java.getAnnotation(SchemaAnnotation::class.java)?.name + val childName = child.value.java.getAnnotation(SchemaAnnotation::class.java)?.name?.ifBlank { null } ?: child.value.java.name.split(".").last() infoMap[childName] = child.name } @@ -884,19 +874,25 @@ class DocumentService { val nullable = mutableMapOf() val kClazz = clazz.kotlin val mock = mockModel(clazz, nullable) -// val mock = try { val res = mutableMapOf() kClazz.memberProperties.forEach { // 编译后,属性默认是private,需要设置isAccessible 才可以读取到值 it.isAccessible = true res[it.name] = SwaggerDocParameterInfo( markedNullable = nullable[it.name] ?: false, - defaultValue = checkDefaultValue(it.call(mock).toString()) + defaultValue = null ) } return res } + private val initClazz: Lazy, Any>> = lazy { + mapOf( + LocalDateTime::class.java to LocalDateTime.now(), + BigDecimal::class.java to BigDecimal.ONE + ) + } + private fun mockModel(clazz: Class<*>, nullable: MutableMap = mutableMapOf()): Any? { if (clazz.simpleName == "Object") { return "" @@ -904,13 +900,13 @@ class DocumentService { if (clazz.isEnum) { return clazz.enumConstants.first() } - if (clazz.simpleName == "GithubRepository") { - println() + if (Modifier.isAbstract(clazz.modifiers)) { + return null } println(clazz.name) val kClazz = clazz.kotlin - if (!kClazz.isData) { - return null + if (clazz in initClazz.value.keys) { + return initClazz.value[clazz] } val constructor = kClazz.constructors.maxByOrNull { it.parameters.size }!! val parameters = constructor.parameters @@ -930,7 +926,9 @@ class DocumentService { arguments[i] = 0 } if (syntheticInit != null) { - arguments[argumentsSize - 2] = offset + if (syntheticInit.parameterTypes.size - parameters.size >= 2) { + arguments[argumentsSize - 2] = offset + } arguments[argumentsSize - 1] = null as DefaultConstructorMarker? } val javaConstructor = constructor.javaConstructor @@ -947,43 +945,43 @@ class DocumentService { @Suppress("ComplexMethod") private fun makeStandardArgument(type: KType, debug: KFunction<*>): Any? { if (type.isMarkedNullable) return null - return when (type.classifier) { - Boolean::class -> false - Byte::class -> 0.toByte() - Short::class -> 0.toShort() - Char::class -> 0.toChar() - Int::class -> 0 - Long::class -> 0L - Float::class -> 0f - Double::class -> 0.0 - String::class -> "" - Enum::class -> { - null + return when { + type.isSubtypeOf(typeOf()) -> false + type.isSubtypeOf(typeOf()) -> 0.toByte() + type.isSubtypeOf(typeOf()) -> 0.toShort() + type.isSubtypeOf(typeOf()) -> 0.toChar() + type.isSubtypeOf(typeOf()) -> 0 + type.isSubtypeOf(typeOf()) -> 0L + type.isSubtypeOf(typeOf()) -> 0f + type.isSubtypeOf(typeOf()) -> 0.0 + type.isSubtypeOf(typeOf()) -> "" + type.isSubtypeOf(typeOf>()) -> { + (type.javaType as Class<*>).enumConstants.firstOrNull() } - Set::class -> { - type.arguments.firstOrNull()?.let { setOf(makeStandardArgument(it.type!!, debug)) } ?: "" + type.isSubtypeOf(typeOf>()) -> { + emptySet() } - List::class -> { - type.arguments.firstOrNull()?.let { listOf(makeStandardArgument(it.type!!, debug)) } ?: "" + type.isSubtypeOf(typeOf>()) -> { + ArrayList() } - ArrayList::class -> { - type.arguments.firstOrNull()?.let { arrayListOf(makeStandardArgument(it.type!!, debug)) } ?: "" + type.isSubtypeOf(typeOf>()) -> { + emptyList() } - Array::class -> { - type.arguments.firstOrNull()?.let { arrayOf(makeStandardArgument(it.type!!, debug)) } ?: "" + type.isSubtypeOf(typeOf>()) -> { + val arrayType = type.arguments.firstOrNull()?.type ?: return emptyArray() + java.lang.reflect.Array.newInstance(arrayType.javaType as Class<*>, 0) } - Map::class -> { - mapOf( - makeStandardArgument( - type.arguments[0].type!!, - debug - ) to makeStandardArgument(type.arguments[1].type!!, debug) - ) + type.isSubtypeOf(typeOf>()) -> { + emptyList() + } + + type.isSubtypeOf(typeOf>()) -> { + emptyMap() } else -> { diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResource.kt index 767c4afb17b4..c09cc0d6937b 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResource.kt @@ -7,10 +7,13 @@ import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import javax.ws.rs.Consumes +import javax.ws.rs.DELETE import javax.ws.rs.GET import javax.ws.rs.HeaderParam import javax.ws.rs.Path +import javax.ws.rs.PathParam import javax.ws.rs.Produces +import javax.ws.rs.QueryParam import javax.ws.rs.core.MediaType @Tag(name = "USER_PIPELINE_VIEW", description = "用户-流水线视图") @@ -26,4 +29,19 @@ interface OpPipelineViewResource { @HeaderParam(AUTH_HEADER_USER_ID) userId: String ): Result + + @Operation(summary = "删除yaml流水线组") + @DELETE + @Path("{projectId}/{repoHashId}/deleteYamlView") + fun deleteYamlView( + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "代码库hashId", required = true) + @PathParam("repoHashId") + repoHashId: String, + @Parameter(description = "yaml文件目录", required = true) + @QueryParam("directory") + directory: String + ): Result } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildParametersResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildParametersResource.kt index 7d08e4257c52..eb5934c70b6e 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildParametersResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildParametersResource.kt @@ -183,4 +183,22 @@ interface UserBuildParametersResource { @QueryParam("search") search: String? ): Result> + + @Operation(summary = "构建表单查询分支/Tag变量") + @GET + @Path("/{projectId}/repository/refs") + fun listRepoRefs( + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "repo hash id", required = true) + @QueryParam("repositoryId") + repositoryId: String, + @Parameter(description = "代码库请求类型", required = false) + @QueryParam("repositoryType") + repositoryType: RepositoryType?, + @Parameter(description = "搜索条件", required = false) + @QueryParam("search") + search: String? + ): Result> } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserPipelineGroupResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserPipelineGroupResource.kt index 781a4c3b1729..25960e0d58e2 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserPipelineGroupResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserPipelineGroupResource.kt @@ -35,9 +35,9 @@ import com.tencent.devops.process.pojo.classify.PipelineGroupCreate import com.tencent.devops.process.pojo.classify.PipelineGroupUpdate import com.tencent.devops.process.pojo.classify.PipelineLabelCreate import com.tencent.devops.process.pojo.classify.PipelineLabelUpdate -import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag import javax.ws.rs.Consumes import javax.ws.rs.DELETE import javax.ws.rs.GET diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/constant/ProcessMessageCode.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/constant/ProcessMessageCode.kt index a7ca239c2de4..95d527275a86 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/constant/ProcessMessageCode.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/constant/ProcessMessageCode.kt @@ -558,4 +558,7 @@ object ProcessMessageCode { // 用户[xxx] 没有如下子流水线的执行权限,重置授权失败 const val BK_NOT_SUB_PIPELINE_EXECUTE_PERMISSION_RESET_ERROR_TITLE = "bkNotSubPipelineExecutePermissionResetErrorTitle" + + // 权限代持人[xxx]已无当前流水线执行权限,可能是权限已过期或不再负责此流水线,请联系流水线拥有者处理 + const val BK_AUTHOR_NOT_PIPELINE_EXECUTE_PERMISSION = "bkAuthorNotPipelineExecutePermission" } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt index ca7b1b858c89..d8ffd3e36298 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/app/StartBuildContext.kt @@ -42,6 +42,7 @@ import com.tencent.devops.common.pipeline.pojo.element.Element import com.tencent.devops.common.pipeline.pojo.element.trigger.enums.CodeType import com.tencent.devops.common.pipeline.pojo.setting.PipelineRunLockType import com.tencent.devops.common.pipeline.pojo.setting.PipelineSetting +import com.tencent.devops.common.pipeline.utils.CascadePropertyUtils import com.tencent.devops.common.pipeline.utils.PIPELINE_GIT_EVENT_URL import com.tencent.devops.common.webhook.pojo.code.BK_REPO_GIT_WEBHOOK_EVENT_TYPE import com.tencent.devops.common.webhook.pojo.code.BK_REPO_GIT_WEBHOOK_ISSUE_IID @@ -441,12 +442,11 @@ data class StartBuildContext( val originStartContexts = HashMap(realStartParamKeys.size, /* loadFactor */ 1F) realStartParamKeys.forEach { key -> pipelineParamMap[key]?.let { param -> - originStartParams.add(param) - if (key.startsWith(CONTEXT_PREFIX)) { - originStartContexts[key] = param + if (CascadePropertyUtils.supportCascadeParam(param.valueType)) { + originStartParams.addAll(fillCascadeParam(param, originStartContexts)) } else { - val ctxKey = CONTEXT_PREFIX + key - originStartContexts[ctxKey] = param.copy(key = ctxKey) + originStartParams.add(param) + fillContextPrefix(param, originStartContexts) } } } @@ -458,5 +458,48 @@ data class StartBuildContext( return originStartParams } + + private fun fillContextPrefix( + param: BuildParameters, + originStartContexts: HashMap + ) { + with(param) { + if (key.startsWith(CONTEXT_PREFIX)) { + originStartContexts[key] = param + } else { + val ctxKey = CONTEXT_PREFIX + key + originStartContexts[ctxKey] = param.copy(key = ctxKey) + } + } + } + + /** + * 根据原始值,填充级联参数 + * xxx = {"repo-name": "xxx/xxx","branch":"master"} + * xxx.repo-name = xxx/xxx + * xxx.branch = master + */ + private fun fillCascadeParam( + param: BuildParameters, + originStartContexts: HashMap + ): List { + val originStartParams = mutableListOf() + val key = param.key + val paramValue = CascadePropertyUtils.parseDefaultValue(key, param.value, param.valueType) + val cascadeParam = param.copy(value = paramValue) + originStartParams.add(cascadeParam) + // 填充下级参数的[variables.] + fillContextPrefix(cascadeParam, originStartContexts) + CascadePropertyUtils.getCascadeVariableKeyMap(key, param.valueType!!) + .forEach { (subKey, paramKey) -> + val subParam = param.copy( + key = paramKey, + value = paramValue[subKey] ?: "" + ) + // 填充下级参数的[variables.] + fillContextPrefix(subParam, originStartContexts) + } + return originStartParams + } } } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/DeployPipelineResult.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/DeployPipelineResult.kt index 7549ea701ddb..3a72f191379d 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/DeployPipelineResult.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/DeployPipelineResult.kt @@ -45,6 +45,6 @@ data class DeployPipelineResult( val targetUrl: String? = null, @get:Schema(title = "yaml信息", required = false) val yamlInfo: PipelineYamlVo? = null, - @get:Schema(title = "是否更新了推荐版本号", required = false) + @get:Schema(title = "是否更新了推荐版本号基准值", required = false) val updateBuildNo: Boolean? = null ) diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/OptionalTemplate.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/OptionalTemplate.kt index 8a1db0eefe1b..2cd6a61b3f51 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/OptionalTemplate.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/OptionalTemplate.kt @@ -75,7 +75,11 @@ data class OptionalTemplate( data class CloneTemplateSettingExist( val notifySettingExist: Boolean, val concurrencySettingExist: Boolean, - val labelSettingExist: Boolean + val labelSettingExist: Boolean, + @get:Schema(title = "是否继承项目流水线语言风格", required = false) + var inheritedDialect: Boolean? = true, + @get:Schema(title = "流水线语言风格", required = false) + var pipelineDialect: String? = null ) { companion object { fun fromSetting(setting: PipelineSetting?, pipelinesWithLabels: Set?): CloneTemplateSettingExist { @@ -85,7 +89,9 @@ data class CloneTemplateSettingExist( return CloneTemplateSettingExist( notifySettingExist = !setting.notifySettingIsNull(), concurrencySettingExist = !setting.concurrencySettingIsNull(), - labelSettingExist = pipelinesWithLabels?.contains(setting.pipelineId) ?: false + labelSettingExist = pipelinesWithLabels?.contains(setting.pipelineId) ?: false, + inheritedDialect = setting.pipelineAsCodeSettings?.inheritedDialect ?: true, + pipelineDialect = setting.pipelineAsCodeSettings?.pipelineDialect ) } } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceParams.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceParams.kt index 6eefebbcc517..68d134af0308 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceParams.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceParams.kt @@ -44,5 +44,7 @@ data class TemplateInstanceParams( @get:Schema(title = "构建号,不建议使用", required = false) val buildNo: BuildNo?, @get:Schema(title = "流水线变量列表", required = false) - val param: List + val param: List, + @get:Schema(title = "是否更新了推荐版本号基准值", required = false) + val updateBuildNo: Boolean? = null ) diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceUpdate.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceUpdate.kt index 3aca9ab58287..f1b2b3886027 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceUpdate.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/template/TemplateInstanceUpdate.kt @@ -44,5 +44,7 @@ data class TemplateInstanceUpdate( @get:Schema(title = "构建版本号", required = false) val buildNo: BuildNo?, @get:Schema(title = "流水线变量列表, 建议先通过v4_app_template_get获取,再按需修改。", required = false) - val param: List? + val param: List?, + @get:Schema(title = "重置实例推荐版本为基准值", required = false) + val resetBuildNo: Boolean? = null ) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/actuate/ProcessHealthIndicator.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/actuate/ProcessHealthIndicator.kt new file mode 100644 index 000000000000..602bdc886934 --- /dev/null +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/actuate/ProcessHealthIndicator.kt @@ -0,0 +1,30 @@ +package com.tencent.devops.process.actuate + +import com.tencent.devops.process.dao.PipelineFavorDao +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.actuate.health.AbstractHealthIndicator +import org.springframework.boot.actuate.health.Health +import org.springframework.stereotype.Component + +@Component +class ProcessHealthIndicator @Autowired constructor( + private val pipelineFavorDao: PipelineFavorDao, + private val dslContext: DSLContext +) : AbstractHealthIndicator() { + @SuppressWarnings("SwallowedException", "TooGenericExceptionCaught") + override fun doHealthCheck(builder: Health.Builder) { + try { + val maxId = pipelineFavorDao.getMaxId(dslContext) + builder.up().withDetail("MaxId", maxId) + } catch (e: Exception) { + logger.error("Get max id failed") + builder.down() + } + } + + companion object { + private val logger = LoggerFactory.getLogger(ProcessHealthIndicator::class.java) + } +} \ No newline at end of file diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineFavorDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineFavorDao.kt index 61d014d3022f..0797ef1f126a 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineFavorDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/dao/PipelineFavorDao.kt @@ -29,11 +29,12 @@ package com.tencent.devops.process.dao import com.tencent.devops.model.process.tables.TPipelineFavor import com.tencent.devops.model.process.tables.records.TPipelineFavorRecord +import java.time.LocalDateTime import org.jooq.DSLContext import org.jooq.Result +import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository -import java.time.LocalDateTime /** * 用户收藏流水线 @@ -138,6 +139,14 @@ class PipelineFavorDao { } } + fun getMaxId( + dslContext: DSLContext + ): Long { + with(TPipelineFavor.T_PIPELINE_FAVOR) { + return dslContext.select(DSL.max(ID)).from(this).fetchOne(0, Long::class.java)!! + } + } + companion object { private val logger = LoggerFactory.getLogger(PipelineFavorDao::class.java) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt index 195c185aad1b..e7ef454ca3ad 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/compatibility/v2/V2BuildParametersCompatibilityTransformer.kt @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory open class V2BuildParametersCompatibilityTransformer : BuildParametersCompatibilityTransformer { + @SuppressWarnings("ComplexMethod") override fun parseTriggerParam( userId: String, projectId: String, @@ -53,22 +54,26 @@ open class V2BuildParametersCompatibilityTransformer : BuildParametersCompatibil // 现有用户覆盖定义旧系统变量的,前端无法帮助转换,用户传的仍然是旧变量为key,则用新的Key无法找到,要用旧的id兜底 // 如果编排中指定为常量,则必须以编排的默认值为准,不支持触发时传参覆盖 - val value = if (param.constant == true) { - // 常量需要在启动是强制设为只读 - param.readOnly = true - param.defaultValue -// } else if (!param.required) { -// // TODO #8161 没有作为前端可填入参的变量,直接取默认值,不可被覆盖(实施前仅打印日志) + val value = when { + param.constant == true -> { + // 常量需要在启动是强制设为只读 + param.readOnly = true + param.defaultValue + } +// !param.required ->{ +// TODO #8161 没有作为前端可填入参的变量,直接取默认值,不可被覆盖(实施前仅打印日志) // param.defaultValue - } else { - val overrideValue = paramValues[key] ?: paramValues[param.id] - if (!param.required && overrideValue != null) { - logger.warn( - "BKSystemErrorMonitor|parseTriggerParam|$userId|$projectId|$pipelineId|[$key] " + - "not required, overrideValue=$overrideValue, defaultValue=${param.defaultValue}" - ) +// } + else -> { + val overrideValue = paramValues[key] ?: paramValues[param.id] + if (!param.required && overrideValue != null) { + logger.warn( + "BKSystemErrorMonitor|parseTriggerParam|$userId|$projectId|$pipelineId|[$key] " + + "not required, overrideValue=$overrideValue, defaultValue=${param.defaultValue}" + ) + } + overrideValue ?: param.defaultValue } - overrideValue ?: param.defaultValue } if (param.valueNotEmpty == true && value.toString().isEmpty()) { throw ErrorCodeException( diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineModelTaskDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineModelTaskDao.kt index bfedd22d63e5..497daccf9385 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineModelTaskDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineModelTaskDao.kt @@ -103,13 +103,17 @@ class PipelineModelTaskDao { */ fun getPipelineCountByAtomCode(dslContext: DSLContext, atomCode: String, projectCode: String?): Int { with(TPipelineModelTask.T_PIPELINE_MODEL_TASK) { - val condition = getListByAtomCodeCond(this, atomCode, projectCode) + val condition = mutableListOf() val tpi = TPipelineInfo.T_PIPELINE_INFO + condition.add(ATOM_CODE.eq(atomCode)) + if (projectCode != null) { + condition.add(tpi.PROJECT_ID.eq(projectCode)) + } condition.add(tpi.CHANNEL.notEqual(ChannelCode.AM.name)) return dslContext.select(DSL.countDistinct(PIPELINE_ID)) .from(this) .join(tpi) - .on(PIPELINE_ID.eq(tpi.PIPELINE_ID)) + .on(PIPELINE_ID.eq(tpi.PIPELINE_ID).and(PROJECT_ID.eq(tpi.PROJECT_ID))) .where(condition) .fetchOne(0, Int::class.java)!! } @@ -126,15 +130,15 @@ class PipelineModelTaskDao { with(TPipelineModelTask.T_PIPELINE_MODEL_TASK) { val condition = mutableListOf() condition.add(ATOM_CODE.`in`(atomCodeList)) + val tpi = TPipelineInfo.T_PIPELINE_INFO if (projectCode != null) { - condition.add(PROJECT_ID.eq(projectCode)) + condition.add(tpi.PROJECT_ID.eq(projectCode)) } - val tpi = TPipelineInfo.T_PIPELINE_INFO condition.add(tpi.CHANNEL.notEqual(ChannelCode.AM.name)) return dslContext.select(DSL.countDistinct(PIPELINE_ID), ATOM_CODE) .from(this) .join(tpi) - .on(PIPELINE_ID.eq(tpi.PIPELINE_ID)) + .on(PIPELINE_ID.eq(tpi.PIPELINE_ID).and(PROJECT_ID.eq(tpi.PROJECT_ID))) .where(condition) .groupBy(ATOM_CODE) .fetch() @@ -229,7 +233,6 @@ class PipelineModelTaskDao { endUpdateTime = endUpdateTime ) val tpi = TPipelineInfo.T_PIPELINE_INFO - condition.add(tpi.CHANNEL.notEqual(ChannelCode.AM.name)) val baseStep = dslContext.select( PIPELINE_ID.`as`(KEY_PIPELINE_ID), PROJECT_ID.`as`(KEY_PROJECT_ID), @@ -237,7 +240,7 @@ class PipelineModelTaskDao { ) .from(this) .join(tpi) - .on(PIPELINE_ID.eq(tpi.PIPELINE_ID)) + .on(PIPELINE_ID.eq(tpi.PIPELINE_ID).and(PROJECT_ID.eq(tpi.PROJECT_ID))) .where(condition) .groupBy(PIPELINE_ID) .orderBy(UPDATE_TIME.desc(), PIPELINE_ID.desc()) @@ -267,7 +270,12 @@ class PipelineModelTaskDao { startUpdateTime = startUpdateTime, endUpdateTime = endUpdateTime ) - return dslContext.select(DSL.countDistinct(PIPELINE_ID)).from(this).where(condition) + val tpi = TPipelineInfo.T_PIPELINE_INFO + return dslContext.select(DSL.countDistinct(PIPELINE_ID)) + .from(this) + .join(tpi) + .on(PIPELINE_ID.eq(tpi.PIPELINE_ID).and(PROJECT_ID.eq(tpi.PROJECT_ID))) + .where(condition) .fetchOne(0, Long::class.java)!! } } @@ -282,9 +290,11 @@ class PipelineModelTaskDao { ): MutableList { val condition = mutableListOf() condition.add(a.ATOM_CODE.eq(atomCode)) + val tpi = TPipelineInfo.T_PIPELINE_INFO if (!projectId.isNullOrEmpty()) { - condition.add(a.PROJECT_ID.eq(projectId)) + condition.add(tpi.PROJECT_ID.eq(projectId)) } + condition.add(tpi.CHANNEL.notEqual(ChannelCode.AM.name)) if (!version.isNullOrEmpty()) { condition.add(a.ATOM_VERSION.contains(version)) } @@ -303,17 +313,14 @@ class PipelineModelTaskDao { pipelineIds: Set ): Result? { with(TPipelineModelTask.T_PIPELINE_MODEL_TASK) { - val condition = getListByAtomCodeCond(this, atomCode, null) - - val baseStep = dslContext.select( + return dslContext.select( PIPELINE_ID.`as`(KEY_PIPELINE_ID), ATOM_VERSION.`as`(KEY_VERSION) ) .from(this) - .where(condition) + .where(ATOM_CODE.eq(atomCode)) .and(PIPELINE_ID.`in`(pipelineIds)) - - return baseStep.fetch() + .fetch() } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineYamlInfoDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineYamlInfoDao.kt index 513aa9e987db..b024f089e1f3 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineYamlInfoDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineYamlInfoDao.kt @@ -226,12 +226,20 @@ class PipelineYamlInfoDao { fun countYamlPipeline( dslContext: DSLContext, projectId: String, - repoHashId: String + repoHashId: String, + directory: String? = null ): Long { return with(TPipelineYamlInfo.T_PIPELINE_YAML_INFO) { dslContext.selectCount().from(this) .where(PROJECT_ID.eq(projectId)) .and(REPO_HASH_ID.eq(repoHashId)) + .let { + if (directory.isNullOrBlank()) { + it + } else { + it.and(DIRECTORY.eq(directory)) + } + } .fetchOne(0, Long::class.java) ?: 0L } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/interceptor/QueueInterceptor.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/interceptor/QueueInterceptor.kt index f61d44dc7468..0ee26a820bca 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/interceptor/QueueInterceptor.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/interceptor/QueueInterceptor.kt @@ -201,7 +201,8 @@ class QueueInterceptor @Autowired constructor( pipelineId = pipelineId, userId = latestStartUser ?: task.pipelineInfo.creator, buildId = buildInfo.buildId, - status = BuildStatus.CANCELED + status = BuildStatus.CANCELED, + executeCount = buildInfo.executeCount ) ) } @@ -255,7 +256,8 @@ class QueueInterceptor @Autowired constructor( pipelineId = pipelineId, userId = latestStartUser ?: task.pipelineInfo.creator, buildId = buildInfo.buildId, - status = BuildStatus.CANCELED + status = BuildStatus.CANCELED, + executeCount = buildInfo.executeCount ) ) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/event/PipelineBuildCancelEvent.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/event/PipelineBuildCancelEvent.kt index 6fe4dfa5d1a7..0e09ffc11e38 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/event/PipelineBuildCancelEvent.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/pojo/event/PipelineBuildCancelEvent.kt @@ -47,6 +47,7 @@ data class PipelineBuildCancelEvent( val buildId: String, val status: BuildStatus = BuildStatus.CANCELED, val buildNum: Int? = null, + val executeCount: Int?, override var actionType: ActionType = ActionType.END, override var delayMills: Int = 2000 ) : IPipelineEvent(actionType, source, projectId, pipelineId, userId, delayMills) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt index b9243b239aa6..6a7138c22d57 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineContainerService.kt @@ -579,7 +579,7 @@ class PipelineContainerService @Autowired constructor( container.startVMTaskSeq = startVMTaskSeq // 构建矩阵永远跟随stage重试,在需要重试的stage中,单独增加重试记录 - if (context.needRerunStage(stage = stage) && container.matrixGroupFlag == true) { + if (container.matrixGroupFlag == true && !context.needSkipWhenStageFailRetry(stage = stage)) { container.retryFreshMatrixOption() cleanContainersInMatrixGroup( transactionContext = dslContext, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt index 6309eff2f9b1..9dfb28a48e6b 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryService.kt @@ -33,7 +33,6 @@ import com.tencent.devops.common.api.constant.CommonMessageCode import com.tencent.devops.common.api.exception.DependNotFoundException import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.exception.InvalidParamException -import com.tencent.devops.common.api.pojo.PipelineAsCodeSettings import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.MessageUtil import com.tencent.devops.common.api.util.Watcher @@ -108,6 +107,7 @@ import com.tencent.devops.process.pojo.pipeline.PipelineResourceVersion import com.tencent.devops.process.pojo.pipeline.PipelineYamlVo import com.tencent.devops.process.pojo.pipeline.TemplateInfo import com.tencent.devops.process.pojo.setting.PipelineModelVersion +import com.tencent.devops.process.service.PipelineAsCodeService import com.tencent.devops.process.service.PipelineOperationLogService import com.tencent.devops.process.service.label.PipelineGroupService import com.tencent.devops.process.service.pipeline.PipelineSettingVersionService @@ -165,7 +165,8 @@ class PipelineRepositoryService constructor( private val transferService: PipelineTransferYamlService, private val redisOperation: RedisOperation, private val pipelineYamlInfoDao: PipelineYamlInfoDao, - private val pipelineGroupService: PipelineGroupService + private val pipelineGroupService: PipelineGroupService, + private val pipelineAsCodeService: PipelineAsCodeService ) { companion object { @@ -226,7 +227,6 @@ class PipelineRepositoryService constructor( yaml: YamlWithVersion? = null, baseVersion: Int? = null, useSubscriptionSettings: Boolean? = false, - useLabelSettings: Boolean? = false, useConcurrencyGroup: Boolean? = false, templateId: String? = null, updateLastModifyUser: Boolean? = true, @@ -234,15 +234,21 @@ class PipelineRepositoryService constructor( versionStatus: VersionStatus? = VersionStatus.RELEASED, branchName: String? = null, description: String? = null, - yamlInfo: PipelineYamlVo? = null, - inheritedDialectSetting: Boolean? = null, - pipelineDialectSetting: String? = null, - pipelineDialect: IPipelineDialect? = null + yamlInfo: PipelineYamlVo? = null ): DeployPipelineResult { // 生成流水线ID,新流水线以p-开头,以区分以前旧数据 val pipelineId = signPipelineId ?: pipelineIdGenerator.getNextId() + val pipelineSetting = if (!create) { + setting ?: pipelineSettingDao.getSetting(dslContext, projectId, pipelineId) + } else { + setting + } + val pipelineDialect = pipelineAsCodeService.getPipelineDialect( + projectId = projectId, + asCodeSettings = pipelineSetting?.pipelineAsCodeSettings + ) val modelTasks = initModel( model = model, projectId = projectId, @@ -276,8 +282,6 @@ class PipelineRepositoryService constructor( } return if (!create) { - val pipelineSetting = setting - ?: pipelineSettingDao.getSetting(dslContext, projectId, pipelineId) val result = update( projectId = projectId, pipelineId = pipelineId, @@ -303,7 +307,7 @@ class PipelineRepositoryService constructor( projectId = projectId, pipelineId = pipelineId, model = model, - customSetting = setting, + customSetting = pipelineSetting, yaml = yaml, userId = userId, channelCode = channelCode, @@ -312,15 +316,12 @@ class PipelineRepositoryService constructor( buildNo = buildNo, modelTasks = modelTasks, useSubscriptionSettings = useSubscriptionSettings, - useLabelSettings = useLabelSettings, useConcurrencyGroup = useConcurrencyGroup, templateId = templateId, versionStatus = versionStatus, branchName = branchName, description = description, - baseVersion = baseVersion, - inheritedDialectSetting = inheritedDialectSetting, - pipelineDialectSetting = pipelineDialectSetting + baseVersion = baseVersion ) } operationLogService.addOperationLog( @@ -627,6 +628,45 @@ class PipelineRepositoryService constructor( ) } + /** + * 初始化默认的流水线setting + */ + fun createDefaultSetting( + projectId: String, + pipelineId: String, + pipelineName: String, + channelCode: ChannelCode + ): PipelineSetting { + // 空白流水线设置初始化 + val maxPipelineResNum = if ( + channelCode.name in versionConfigure.specChannels.split(",") + ) { + versionConfigure.specChannelMaxKeepNum + } else { + versionConfigure.maxKeepNum + } + val notifyTypes = if (channelCode == ChannelCode.BS) { + pipelineInfoExtService.failNotifyChannel() + } else { + "" + } + val failType = notifyTypes.split(",").filter { i -> i.isNotBlank() } + .map { type -> PipelineSubscriptionType.valueOf(type) }.toSet() + val failSubscription = Subscription( + types = failType, + groups = emptySet(), + users = "\${{ci.actor}}", + content = NotifyTemplateUtils.getCommonShutdownFailureContent() + ).takeIf { failType.isNotEmpty() } + return PipelineSetting.defaultSetting( + projectId = projectId, + pipelineId = pipelineId, + pipelineName = pipelineName, + maxPipelineResNum = maxPipelineResNum, + failSubscription = failSubscription + ) + } + private fun create( projectId: String, pipelineId: String, @@ -641,14 +681,11 @@ class PipelineRepositoryService constructor( modelTasks: Collection, baseVersion: Int?, useSubscriptionSettings: Boolean? = false, - useLabelSettings: Boolean? = false, useConcurrencyGroup: Boolean? = false, templateId: String? = null, versionStatus: VersionStatus? = VersionStatus.RELEASED, branchName: String?, - description: String?, - inheritedDialectSetting: Boolean? = true, - pipelineDialectSetting: String? = null + description: String? ): DeployPipelineResult { // #8161 如果只有一个草稿版本的创建操作,流水线状态也为仅有草稿 val modelVersion = 1 @@ -692,35 +729,11 @@ class PipelineRepositoryService constructor( pipelineName = model.name, desc = model.desc ?: "" ) ?: run { - // 空白流水线设置初始化 - val maxPipelineResNum = if ( - channelCode.name in versionConfigure.specChannels.split(",") - ) { - versionConfigure.specChannelMaxKeepNum - } else { - versionConfigure.maxKeepNum - } - val notifyTypes = if (channelCode == ChannelCode.BS) { - pipelineInfoExtService.failNotifyChannel() - } else { - "" - } - val failType = notifyTypes.split(",").filter { i -> i.isNotBlank() } - .map { type -> PipelineSubscriptionType.valueOf(type) }.toSet() - val failSubscription = Subscription( - types = failType, - groups = emptySet(), - users = "\${{ci.actor}}", - content = NotifyTemplateUtils.getCommonShutdownFailureContent() - ).takeIf { failType.isNotEmpty() } - PipelineSetting.defaultSetting( + createDefaultSetting( projectId = projectId, pipelineId = pipelineId, pipelineName = model.name, - maxPipelineResNum = maxPipelineResNum, - failSubscription = failSubscription, - inheritedDialectSetting = inheritedDialectSetting, - pipelineDialectSetting = pipelineDialectSetting + channelCode = channelCode ) } @@ -729,42 +742,19 @@ class PipelineRepositoryService constructor( if (useTemplateSettings( templateId = templateId, useSubscriptionSettings = useSubscriptionSettings, - useLabelSettings = useLabelSettings, useConcurrencyGroup = useConcurrencyGroup ) ) { // 沿用模板的配置 val setting = getSetting(projectId, templateId!!) ?: throw ErrorCodeException(errorCode = ProcessMessageCode.PIPELINE_SETTING_NOT_EXISTS) - setting.pipelineId = pipelineId - setting.pipelineName = model.name setting.version = settingVersion - if (useSubscriptionSettings != true) { - setting.successSubscription = null - setting.successSubscriptionList = null - setting.failSubscription = null - setting.failSubscriptionList = newSetting.failSubscriptionList + if (useSubscriptionSettings == true) { + newSetting.copySubscriptionSettings(setting) } - if (useConcurrencyGroup != true) { - setting.concurrencyGroup = null - setting.concurrencyCancelInProgress = false - setting.maxConRunningQueueSize = PIPELINE_SETTING_MAX_CON_QUEUE_SIZE_MAX + if (useConcurrencyGroup == true) { + newSetting.copyConcurrencyGroup(setting) } - if (useLabelSettings != true) { - setting.labels = listOf() - } else { - val groups = pipelineGroupService.getGroups(userId, projectId, templateId) - val labels = ArrayList() - groups.forEach { - labels.addAll(it.labels) - } - setting.labels = labels - } - setting.pipelineAsCodeSettings = PipelineAsCodeSettings.initDialect( - inheritedDialect = inheritedDialectSetting, - pipelineDialect = pipelineDialectSetting - ) - newSetting = setting } // 如果不需要覆盖模板内容,则直接保存传值或默认值 pipelineSettingDao.saveSetting(transactionContext, newSetting) @@ -878,11 +868,10 @@ class PipelineRepositoryService constructor( private fun useTemplateSettings( templateId: String? = null, useSubscriptionSettings: Boolean? = false, - useLabelSettings: Boolean? = false, useConcurrencyGroup: Boolean? = false ): Boolean { return templateId != null && - (useSubscriptionSettings == true || useConcurrencyGroup == true || useLabelSettings == true) + (useSubscriptionSettings == true || useConcurrencyGroup == true) } private fun update( @@ -1726,7 +1715,7 @@ class PipelineRepositoryService constructor( ): PipelineName { setting.checkParam() - if (isPipelineExist( + if (!isTemplate && isPipelineExist( projectId = setting.projectId, excludePipelineId = setting.pipelineId, pipelineName = setting.pipelineName @@ -1772,7 +1761,7 @@ class PipelineRepositoryService constructor( if (old?.pipelineName != null) { oldName = old.pipelineName } - if (versionStatus.isReleasing()) pipelineInfoDao.update( + if (!isTemplate && versionStatus.isReleasing()) pipelineInfoDao.update( dslContext = transactionContext, projectId = setting.projectId, pipelineId = setting.pipelineId, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt index 9d9c88ca1dc8..9f6bd9fdd09b 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt @@ -212,7 +212,8 @@ class PipelineRuntimeService @Autowired constructor( pipelineId = pipelineId, userId = userId, buildId = build.buildId, - status = BuildStatus.TERMINATE + status = BuildStatus.TERMINATE, + executeCount = build.executeCount ) ) } @@ -701,7 +702,8 @@ class PipelineRuntimeService @Autowired constructor( userId = userId, buildId = buildId, status = buildStatus, - actionType = actionType + actionType = actionType, + executeCount = executeCount ), PipelineBuildCancelBroadCastEvent( source = "cancelBuild", diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt index f154334063b6..845bae89dc43 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/vmbuild/EngineVMBuildService.kt @@ -110,12 +110,12 @@ import com.tencent.devops.process.utils.PIPELINE_VMSEQ_ID import com.tencent.devops.process.utils.PipelineVarUtil import com.tencent.devops.store.api.container.ServiceContainerAppResource import com.tencent.devops.store.pojo.app.BuildEnv -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Service import java.time.LocalDateTime import java.util.concurrent.TimeUnit import javax.ws.rs.NotFoundException +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service @Suppress( "LongMethod", @@ -193,7 +193,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( ): BuildVariables { val buildInfo = pipelineRuntimeService.getBuildInfo(projectId, buildId) ?: throw NotFoundException("Fail to find build: buildId($buildId)") - Preconditions.checkNotNull(buildInfo, NotFoundException("Pipeline build ($buildId) is not exist")) + Preconditions.checkNotNull(buildInfo) { NotFoundException("Pipeline build ($buildId) is not exist") } LOG.info("ENGINE|$buildId|BUILD_VM_START|j($vmSeqId)|vmName($vmName)") // var表中获取环境变量,并对老版本变量进行兼容 val pipelineId = buildInfo.pipelineId @@ -204,7 +204,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( val asCodeSettings = pipelineAsCodeService.getPipelineAsCodeSettings( projectId = projectId, pipelineId = buildInfo.pipelineId ) - Preconditions.checkNotNull(model, NotFoundException("Build Model ($buildId) is not exist")) + Preconditions.checkNotNull(model) { NotFoundException("Build Model ($buildId) is not exist") } model!!.stages.forEachIndexed { index, s -> if (index == 0) { @@ -1142,6 +1142,7 @@ class EngineVMBuildService @Autowired(required = false) constructor( ErrorType.THIRD_PARTY -> "Please contact the third-party service provider." ErrorType.PLUGIN -> "Please contact the plugin developer." ErrorType.SYSTEM -> "Please contact platform." + ErrorType.BUILD_MACHINE -> "Please contact the person in charge of the build machine." } if (showAI(task)) { buildLogPrinter.addAIErrorLine( diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/CodeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/CodeService.kt index fb5b26db27aa..e9c67290d896 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/CodeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/CodeService.kt @@ -59,9 +59,8 @@ class CodeService @Autowired constructor( private val scmProxyService: ScmProxyService, private val client: Client ) { - fun getSvnDirectories(projectId: String, repoHashId: String?, relativePath: String?): List { - val repositoryConfig = getRepositoryConfig(repoHashId, null) - + fun getSvnDirectories(projectId: String, repositoryConfig: RepositoryConfig, relativePath: String?): List { + val repoHashId = repositoryConfig.getRepositoryId() val repository = (client.get(ServiceRepositoryResource::class).get( projectId = projectId, repositoryId = repositoryConfig.getURLEncodeRepositoryId(), @@ -70,13 +69,13 @@ class CodeService @Autowired constructor( ?: throw NotFoundException( I18nUtil.getCodeLanMessage( messageCode = GIT_NOT_FOUND, - params = arrayOf("$repoHashId") + params = arrayOf(repoHashId) ) )) as? CodeSvnRepository ?: throw IllegalArgumentException( I18nUtil.getCodeLanMessage( messageCode = NOT_SVN_CODE_BASE, - params = arrayOf("$repoHashId") + params = arrayOf(repoHashId) ) ) @@ -119,8 +118,12 @@ class CodeService @Autowired constructor( } } - fun getGitRefs(projectId: String, repoHashId: String?, search: String? = null): List { + fun getSvnDirectories(projectId: String, repoHashId: String?, relativePath: String?): List { val repositoryConfig = getRepositoryConfig(repoHashId, null) + return getSvnDirectories(projectId, repositoryConfig, relativePath) + } + + fun getGitRefs(projectId: String, repositoryConfig: RepositoryConfig, search: String? = null): List { val result = mutableListOf() val branches = scmProxyService.listBranches(projectId, repositoryConfig, search).data ?: listOf() val tags = scmProxyService.listTags(projectId, repositoryConfig, search).data ?: listOf() @@ -130,6 +133,11 @@ class CodeService @Autowired constructor( return result } + fun getGitRefs(projectId: String, repoHashId: String?, search: String? = null): List { + val repositoryConfig = getRepositoryConfig(repoHashId, null) + return getGitRefs(projectId, repositoryConfig, search) + } + fun listRepository(projectId: String, scmType: ScmType): List { try { val result = client.get(ServiceRepositoryResource::class).list(projectId, scmType) @@ -232,7 +240,7 @@ class CodeService @Autowired constructor( ) } - private fun getRepositoryConfig(repoHashId: String?, repoName: String?): RepositoryConfig { + fun getRepositoryConfig(repoHashId: String?, repoName: String?): RepositoryConfig { if (!repoHashId.isNullOrBlank()) { return RepositoryConfig(repoHashId, null, RepositoryType.ID) } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/ProjectCacheService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/ProjectCacheService.kt index a52e95608c6a..e034b44705b9 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/ProjectCacheService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/ProjectCacheService.kt @@ -80,6 +80,6 @@ class ProjectCacheService @Autowired constructor(private val client: Client) { companion object { private val logger = LoggerFactory.getLogger(ProjectCacheService::class.java) private const val cacheSize: Long = 5000 - private const val cacheTimeMinute: Long = 10 + private const val cacheTimeMinute: Long = 5 } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/label/PipelineGroupService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/label/PipelineGroupService.kt index c2025cbef475..6197271554be 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/label/PipelineGroupService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/label/PipelineGroupService.kt @@ -32,6 +32,7 @@ import com.tencent.devops.common.api.exception.OperationException import com.tencent.devops.common.api.util.HashUtil import com.tencent.devops.common.api.util.timestamp import com.tencent.devops.common.client.Client +import com.tencent.devops.common.event.dispatcher.SampleEventDispatcher import com.tencent.devops.common.event.enums.PipelineLabelChangeTypeEnum import com.tencent.devops.common.event.pojo.measure.LabelChangeMetricsBroadCastEvent import com.tencent.devops.common.event.pojo.measure.PipelineLabelRelateInfo @@ -55,9 +56,7 @@ import com.tencent.devops.process.pojo.classify.PipelineGroupWithLabels import com.tencent.devops.process.pojo.classify.PipelineLabel import com.tencent.devops.process.pojo.classify.PipelineLabelCreate import com.tencent.devops.process.pojo.classify.PipelineLabelUpdate -import com.tencent.devops.common.event.dispatcher.SampleEventDispatcher import com.tencent.devops.project.api.service.ServiceAllocIdResource -import java.time.LocalDateTime import org.jooq.DSLContext import org.jooq.Result import org.jooq.impl.DSL @@ -65,6 +64,7 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.dao.DuplicateKeyException import org.springframework.stereotype.Service +import java.time.LocalDateTime @Suppress("ALL") @Service diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchBuildLessDockerStartupTaskAtom.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchBuildLessDockerStartupTaskAtom.kt index 6af7c45c5e1f..e8af3cfbdc99 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchBuildLessDockerStartupTaskAtom.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchBuildLessDockerStartupTaskAtom.kt @@ -146,28 +146,32 @@ class DispatchBuildLessDockerStartupTaskAtom @Autowired constructor( val vmSeqId = task.containerId val pipelineInfo = pipelineRepositoryService.getPipelineInfo(projectId, pipelineId) - Preconditions.checkNotNull(pipelineInfo, BuildTaskException( - errorType = ErrorType.SYSTEM, - errorCode = ERROR_PIPELINE_NOT_EXISTS.toInt(), - errorMsg = - I18nUtil.getCodeLanMessage(messageCode = ERROR_PIPELINE_NOT_EXISTS, params = arrayOf(pipelineId)), - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId - )) + Preconditions.checkNotNull(pipelineInfo) { + BuildTaskException( + errorType = ErrorType.SYSTEM, + errorCode = ERROR_PIPELINE_NOT_EXISTS.toInt(), + errorMsg = + I18nUtil.getCodeLanMessage(messageCode = ERROR_PIPELINE_NOT_EXISTS, params = arrayOf(pipelineId)), + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId + ) + } val container = containerBuildDetailService.getBuildModel(projectId, buildId)?.getContainer(vmSeqId) - Preconditions.checkNotNull(container, BuildTaskException( - errorType = ErrorType.SYSTEM, - errorCode = ERROR_PIPELINE_NODEL_CONTAINER_NOT_EXISTS.toInt(), - errorMsg = I18nUtil.getCodeLanMessage( - messageCode = ERROR_PIPELINE_NOT_EXISTS, - params = arrayOf(vmSeqId) - ), - pipelineId = pipelineId, - buildId = buildId, - taskId = taskId - )) + Preconditions.checkNotNull(container) { + BuildTaskException( + errorType = ErrorType.SYSTEM, + errorCode = ERROR_PIPELINE_NODEL_CONTAINER_NOT_EXISTS.toInt(), + errorMsg = I18nUtil.getCodeLanMessage( + messageCode = ERROR_PIPELINE_NOT_EXISTS, + params = arrayOf(vmSeqId) + ), + pipelineId = pipelineId, + buildId = buildId, + taskId = taskId + ) + } containerBuildRecordService.containerPreparing( projectId = projectId, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchVMStartupTaskAtom.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchVMStartupTaskAtom.kt index 3aedb903f5f3..af09a497935e 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchVMStartupTaskAtom.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/atom/vm/DispatchVMStartupTaskAtom.kt @@ -67,15 +67,15 @@ import com.tencent.devops.process.engine.service.record.ContainerBuildRecordServ import com.tencent.devops.process.pojo.mq.PipelineAgentShutdownEvent import com.tencent.devops.process.pojo.mq.PipelineAgentStartupEvent import com.tencent.devops.process.service.PipelineContextService -import com.tencent.devops.process.utils.PIPELINE_DIALECT import com.tencent.devops.process.utils.BK_CI_AUTHORIZER +import com.tencent.devops.process.utils.PIPELINE_DIALECT import com.tencent.devops.store.api.container.ServiceContainerAppResource +import java.util.concurrent.TimeUnit import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.config.ConfigurableBeanFactory import org.springframework.context.annotation.Scope import org.springframework.stereotype.Component -import java.util.concurrent.TimeUnit /** * @@ -190,9 +190,8 @@ class DispatchVMStartupTaskAtom @Autowired constructor( val vmNames = param.vmNames.joinToString(",") val pipelineInfo = pipelineRepositoryService.getPipelineInfo(projectId, pipelineId) - Preconditions.checkNotNull( - obj = pipelineInfo, - exception = BuildTaskException( + Preconditions.checkNotNull(pipelineInfo) { + BuildTaskException( errorType = ErrorType.SYSTEM, errorCode = ERROR_PIPELINE_NOT_EXISTS.toInt(), errorMsg = MessageUtil.getMessageByLocale( @@ -203,12 +202,11 @@ class DispatchVMStartupTaskAtom @Autowired constructor( buildId = buildId, taskId = taskId ) - ) + } val container = containerBuildDetailService.getBuildModel(projectId, buildId)?.getContainer(vmSeqId) - Preconditions.checkNotNull( - obj = container, - exception = BuildTaskException( + Preconditions.checkNotNull(container) { + BuildTaskException( errorType = ErrorType.SYSTEM, errorCode = ERROR_PIPELINE_NODEL_CONTAINER_NOT_EXISTS.toInt(), errorMsg = MessageUtil.getMessageByLocale( @@ -220,7 +218,7 @@ class DispatchVMStartupTaskAtom @Autowired constructor( buildId = buildId, taskId = taskId ) - ) + } // 这个任务是在构建子流程启动的,所以必须使用根流程进程ID // 注意区分buildId和vmSeqId,BuildId是一次构建整体的ID, diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildCancelControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildCancelControl.kt index f3eda3eb0789..3fa9189adec2 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildCancelControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildCancelControl.kt @@ -118,6 +118,11 @@ class BuildCancelControl @Autowired constructor( LOG.info("[$$buildId|${event.source}|REPEAT_CANCEL_EVENT|${event.status}| abandon!") return false } + // 执行次数不匹配的时间直接丢弃,防止异步延迟 + if (event.executeCount?.let { buildInfo.executeCount != it } == true) { + LOG.info("[$$buildId|${event.source}|EXECUTE_COUNT_NOT_MATCH|${event.status}| abandon!") + return false + } val model = pipelineBuildDetailService.getBuildModel(projectId = event.projectId, buildId = buildId) return if (model != null) { diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt index fe5ce51f6940..33bb10f026a0 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/BuildStartControl.kt @@ -773,7 +773,8 @@ class BuildStartControl @Autowired constructor( pipelineEventDispatcher.dispatch( PipelineBuildCancelEvent( source = TAG, projectId = projectId, pipelineId = pipelineId, - userId = userId, buildId = buildId, status = BuildStatus.UNEXEC + userId = userId, buildId = buildId, status = BuildStatus.UNEXEC, + executeCount = executeCount ) ) return // model不存在直接取消构建 diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/HeartbeatControl.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/HeartbeatControl.kt index 3317f088e228..9dfc28249c7e 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/HeartbeatControl.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/HeartbeatControl.kt @@ -144,7 +144,7 @@ class HeartbeatControl @Autowired constructor( actionType = ActionType.TERMINATE, executeCount = container.executeCount, reason = tipMessage, - errorTypeName = ErrorType.THIRD_PARTY.name, + errorTypeName = ErrorType.BUILD_MACHINE.name, errorCode = ErrorCode.THIRD_PARTY_BUILD_ENV_ERROR ) ) diff --git a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt index 177f45ca6fe3..61343b828b57 100644 --- a/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt +++ b/src/backend/ci/core/process/biz-engine/src/main/kotlin/com/tencent/devops/process/engine/control/command/container/impl/StartActionTaskContainerCmd.kt @@ -389,6 +389,11 @@ class StartActionTaskContainerCmd( BuildStatus.UNEXEC } pipelineTaskService.updateTaskStatus(task = this, userId = starter, buildStatus = taskStatus) + taskBuildRecordService.updateTaskStatus( + projectId = projectId, pipelineId = pipelineId, buildId = buildId, + stageId = stageId, containerId = containerId, taskId = taskId, + executeCount = executeCount ?: 1, buildStatus = taskStatus, operation = "taskNeedTerminate" + ) // 打印构建日志 message.append( I18nUtil.getCodeLanMessage( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServicePipelineVersionResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServicePipelineVersionResourceImpl.kt index 27193e34666b..a231830d3bf3 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServicePipelineVersionResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServicePipelineVersionResourceImpl.kt @@ -246,7 +246,7 @@ class ServicePipelineVersionResourceImpl @Autowired constructor( Audit( resourceType = AuthResourceType.PIPELINE_DEFAULT.value, resourceId = result.pipelineId, - resourceName = modelAndYaml.modelAndSetting.model.name, + resourceName = result.pipelineName, userId = userId, action = "edit", actionContent = "Save Ver.${result.version}", diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildParametersResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildParametersResourceImpl.kt index 756b013c355a..d7cc646cce6c 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildParametersResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildParametersResourceImpl.kt @@ -27,7 +27,9 @@ package com.tencent.devops.process.api +import com.tencent.devops.common.api.enums.RepositoryConfig import com.tencent.devops.common.api.enums.RepositoryType +import com.tencent.devops.common.api.enums.ScmType import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.client.Client import com.tencent.devops.common.pipeline.pojo.BuildFormValue @@ -187,8 +189,59 @@ class UserBuildParametersResourceImpl @Autowired constructor( repositoryType: RepositoryType?, search: String? ): Result> { - val result = mutableListOf() val repositoryConfig = RepositoryConfigUtils.buildConfig(repositoryId, repositoryType) + return Result( + getGitRefs( + projectId = projectId, + repositoryConfig = repositoryConfig, + search = search + ).map { BuildFormValue(it, it) } + ) + } + + override fun listRepoRefs( + projectId: String, + repositoryId: String, + repositoryType: RepositoryType?, + search: String? + ): Result> { + val repositoryConfig = RepositoryConfigUtils.buildConfig(repositoryId, repositoryType) + val repoScmType = scmProxyService.getRepo( + projectId = projectId, + repositoryConfig = repositoryConfig + ).getScmType() + val formValues = when (repoScmType) { + // Git库需要拉分支和Tag + in listOf(ScmType.CODE_GIT, ScmType.CODE_TGIT, ScmType.CODE_GITLAB, ScmType.GITHUB) -> { + getGitRefs( + projectId = projectId, + repositoryConfig = repositoryConfig, + search = search + ) + } + + // Svn库仅拉分支 + ScmType.CODE_SVN -> { + scmProxyService.listBranches( + projectId = projectId, + repositoryConfig = repositoryConfig, + search = search + ).data ?: listOf() + } + + else -> { + throw IllegalArgumentException("Unknown repo type($repoScmType)") + } + }.map { BuildFormValue(it, it) } + return Result(formValues) + } + + private fun getGitRefs( + projectId: String, + repositoryConfig: RepositoryConfig, + search: String? + ): List { + val result = mutableListOf() val branches = scmProxyService.listBranches( projectId = projectId, repositoryConfig = repositoryConfig, @@ -201,8 +254,6 @@ class UserBuildParametersResourceImpl @Autowired constructor( ).data ?: listOf() result.addAll(branches) result.addAll(tags) - return Result( - result.map { BuildFormValue(it, it) } - ) + return result } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserPipelineVersionResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserPipelineVersionResourceImpl.kt index 8941a67fc84a..2faeba52118b 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserPipelineVersionResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserPipelineVersionResourceImpl.kt @@ -247,7 +247,7 @@ class UserPipelineVersionResourceImpl @Autowired constructor( Audit( resourceType = AuthResourceType.PIPELINE_DEFAULT.value, resourceId = result.pipelineId, - resourceName = modelAndYaml.modelAndSetting.model.name, + resourceName = result.pipelineName, userId = userId, action = "edit", actionContent = "Save Ver.${result.version}", @@ -497,7 +497,7 @@ class UserPipelineVersionResourceImpl @Autowired constructor( userId = userId, projectId = projectId, pipelineId = pipelineId, - buildNo = buildNo + targetBuildNo = buildNo.currentBuildNo ) return Result(true) } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResourceImpl.kt index 320f74f85128..6a7de4745b88 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/op/OpPipelineViewResourceImpl.kt @@ -3,14 +3,29 @@ package com.tencent.devops.process.api.op import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.web.RestResource import com.tencent.devops.process.service.view.PipelineViewGroupService +import com.tencent.devops.process.yaml.PipelineYamlViewService import org.springframework.beans.factory.annotation.Autowired @RestResource class OpPipelineViewResourceImpl @Autowired constructor( - private val pipelineViewGroupService: PipelineViewGroupService + private val pipelineViewGroupService: PipelineViewGroupService, + private val pipelineYamlViewService: PipelineYamlViewService ) : OpPipelineViewResource { override fun initAllView(userId: String): Result { Thread { pipelineViewGroupService.initAllView() }.start() return Result(true) } + + override fun deleteYamlView( + projectId: String, + repoHashId: String, + directory: String + ): Result { + pipelineYamlViewService.deleteYamlView( + projectId = projectId, + repoHashId = repoHashId, + directory = directory + ) + return Result(true) + } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/dao/PipelineTriggerEventDao.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/dao/PipelineTriggerEventDao.kt index 9ea38ff78850..5e3316031c0d 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/dao/PipelineTriggerEventDao.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/dao/PipelineTriggerEventDao.kt @@ -461,7 +461,7 @@ class PipelineTriggerEventDao { dslContext.selectFrom(this) .where(EVENT_ID.eq(eventId)) .and(PROJECT_ID.eq(projectId)) - .fetchOne() + .fetchAny() } return record?.let { convertEvent(it) } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/ParamFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/ParamFacadeService.kt index 44144dbe5e85..cf8648e30acb 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/ParamFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/ParamFacadeService.kt @@ -39,6 +39,7 @@ import com.tencent.devops.common.pipeline.enums.BuildFormPropertyType import com.tencent.devops.common.pipeline.enums.ChannelCode import com.tencent.devops.common.pipeline.pojo.BuildFormProperty import com.tencent.devops.common.pipeline.pojo.BuildFormValue +import com.tencent.devops.common.pipeline.pojo.cascade.RepoRefCascadeParam import com.tencent.devops.common.service.utils.LogUtils import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.process.constant.ProcessMessageCode.ERROR_SUB_PIPELINE_PARAM_FILTER_FAILED @@ -86,6 +87,8 @@ class ParamFacadeService @Autowired constructor( filterParams.add(addArtifactoryProperties(userId, projectId, it)) } else if (it.type == BuildFormPropertyType.SUB_PIPELINE) { filterParams.add(addSubPipelineProperties(userId, projectId, pipelineId, it)) + } else if (it.type == BuildFormPropertyType.REPO_REF) { + filterParams.add(addRepoRefs(projectId, it)) } else { filterParams.add(it) } @@ -114,6 +117,8 @@ class ParamFacadeService @Autowired constructor( addArtifactoryProperties(userId, projectId, property) } else if (property.type == BuildFormPropertyType.SUB_PIPELINE) { addSubPipelineProperties(userId, projectId, pipelineId, property) + } else if (property.type == BuildFormPropertyType.REPO_REF) { + addRepoRefs(projectId, property) } else { property } @@ -334,7 +339,8 @@ class ParamFacadeService @Autowired constructor( glob = property.glob, properties = property.properties, searchUrl = searchUrl, - replaceKey = replaceKey + replaceKey = replaceKey, + valueNotEmpty = property.valueNotEmpty ) } @@ -407,6 +413,22 @@ class ParamFacadeService @Autowired constructor( } } + private fun addRepoRefs( + projectId: String, + formProperty: BuildFormProperty + ): BuildFormProperty { + return copyFormProperty( + property = formProperty, + options = listOf() + ).let { + it.cascadeProps = RepoRefCascadeParam().getProps( + projectId = projectId, + prop = formProperty + ) + it + } + } + companion object { private val logger = LoggerFactory.getLogger(ParamFacadeService::class.java) } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineAtomService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineAtomService.kt index 33750f2cc745..55bb0bd7a071 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineAtomService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineAtomService.kt @@ -374,7 +374,11 @@ class PipelineAtomService @Autowired constructor( private fun validateUserAtomPermission(atomCode: String, userId: String) { val validateResult = - client.get(ServiceStoreResource::class).isStoreMember(atomCode, StoreTypeEnum.ATOM, userId) + client.get(ServiceStoreResource::class).validatePipelineUserStorePermission( + storeCode = atomCode, + storeType = StoreTypeEnum.ATOM, + userId = userId + ) if (validateResult.isNotOk()) { throw ErrorCodeException( errorCode = validateResult.status.toString(), diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineInfoFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineInfoFacadeService.kt index 42ebeaddb495..1f6986a62fa5 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineInfoFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineInfoFacadeService.kt @@ -62,7 +62,6 @@ import com.tencent.devops.common.pipeline.enums.VersionStatus import com.tencent.devops.common.pipeline.extend.ModelCheckPlugin import com.tencent.devops.common.pipeline.pojo.BuildFormProperty import com.tencent.devops.common.pipeline.pojo.BuildNo -import com.tencent.devops.common.pipeline.pojo.BuildNoUpdateReq import com.tencent.devops.common.pipeline.pojo.PipelineModelAndSetting import com.tencent.devops.common.pipeline.pojo.element.atom.BeforeDeleteParam import com.tencent.devops.common.pipeline.pojo.setting.PipelineSetting @@ -103,13 +102,6 @@ import com.tencent.devops.process.template.service.TemplateService import com.tencent.devops.process.yaml.PipelineYamlFacadeService import com.tencent.devops.process.yaml.transfer.aspect.IPipelineTransferAspect import com.tencent.devops.store.api.template.ServiceTemplateResource -import org.jooq.DSLContext -import org.jooq.impl.DSL -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value -import org.springframework.dao.DuplicateKeyException -import org.springframework.stereotype.Service import java.net.URLEncoder import java.time.LocalDateTime import java.util.LinkedList @@ -117,6 +109,13 @@ import java.util.concurrent.TimeUnit import javax.ws.rs.core.MediaType import javax.ws.rs.core.Response import javax.ws.rs.core.StreamingOutput +import org.jooq.DSLContext +import org.jooq.impl.DSL +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.dao.DuplicateKeyException +import org.springframework.stereotype.Service @Suppress("ALL") @Service @@ -307,12 +306,9 @@ class PipelineInfoFacadeService @Autowired constructor( versionStatus: VersionStatus? = VersionStatus.RELEASED, branchName: String? = null, useSubscriptionSettings: Boolean? = false, - useLabelSettings: Boolean? = false, useConcurrencyGroup: Boolean? = false, description: String? = null, - yamlInfo: PipelineYamlVo? = null, - inheritedDialectSetting: Boolean? = true, - pipelineDialectSetting: String? = null + yamlInfo: PipelineYamlVo? = null ): DeployPipelineResult { val watcher = Watcher(id = "createPipeline|$projectId|$userId|$channelCode|$checkPermission|$instanceType|$fixPipelineId") @@ -432,12 +428,6 @@ class PipelineInfoFacadeService @Autowired constructor( } watcher.start("deployPipeline") - val pipelineDialect = pipelineAsCodeService.getPipelineDialect( - projectId = projectId, - asCodeSettings = setting?.pipelineAsCodeSettings, - inheritedDialectSetting = inheritedDialectSetting, - pipelineDialectSetting = pipelineDialectSetting - ) val result = pipelineRepositoryService.deployPipeline( model = instance, setting = setting, @@ -447,7 +437,6 @@ class PipelineInfoFacadeService @Autowired constructor( channelCode = channelCode, create = true, useSubscriptionSettings = useSubscriptionSettings, - useLabelSettings = useLabelSettings, useConcurrencyGroup = useConcurrencyGroup, versionStatus = versionStatus, branchName = branchName, @@ -455,31 +444,13 @@ class PipelineInfoFacadeService @Autowired constructor( description = description, yaml = yaml, baseVersion = null, - yamlInfo = yamlInfo, - inheritedDialectSetting = inheritedDialectSetting, - pipelineDialectSetting = pipelineDialectSetting, - pipelineDialect = pipelineDialect + yamlInfo = yamlInfo ) pipelineId = result.pipelineId watcher.stop() // 先进行模板关联操作 if (templateId != null) { - watcher.start("addLabel") - if (useLabelSettings == true || versionStatus == VersionStatus.RELEASED) { - val groups = pipelineGroupService.getGroups(userId, projectId, templateId) - val labels = ArrayList() - groups.forEach { - labels.addAll(it.labels) - } - pipelineGroupService.updatePipelineLabel( - userId = userId, - projectId = projectId, - pipelineId = pipelineId, - labelIds = labels - ) - } - watcher.stop() watcher.start("createTemplate") templateService.createRelationBtwTemplate( userId = userId, @@ -969,26 +940,14 @@ class PipelineInfoFacadeService @Autowired constructor( labels = pipelineCopy.labels ) modelCheckPlugin.clearUpModel(copyMode) - val newPipelineId = createPipeline(userId, projectId, copyMode, channelCode).pipelineId val settingInfo = pipelineSettingFacadeService.getSettingInfo(projectId, pipelineId) - if (settingInfo != null) { - // setting pipeline需替换成新流水线的 - val newSetting = pipelineSettingFacadeService.rebuildSetting( - oldSetting = settingInfo, - projectId = projectId, - newPipelineId = newPipelineId, - pipelineName = pipelineCopy.name - ) - // 复制setting到新流水线 - pipelineSettingFacadeService.saveSetting( - userId = userId, - projectId = projectId, - pipelineId = pipelineId, - setting = newSetting, - dispatchPipelineUpdateEvent = false, - updateLabels = false - ) - } + val newPipelineId = createPipeline( + userId = userId, + projectId = projectId, + model = copyMode, + channelCode = channelCode, + setting = settingInfo + ).pipelineId return newPipelineId } catch (e: JsonParseException) { logger.error("Parse process($pipelineId) fail", e) @@ -1125,11 +1084,6 @@ class PipelineInfoFacadeService @Autowired constructor( ) modelCheckPlugin.beforeDeleteElementInExistsModel(existModel, model, param) } - val pipelineSetting = savedSetting ?: pipelineSettingFacadeService.getSettingInfo(projectId, pipelineId) - val pipelineDialect = pipelineAsCodeService.getPipelineDialect( - projectId = projectId, - pipelineSetting?.pipelineAsCodeSettings - ) val deployResult = pipelineRepositoryService.deployPipeline( model = model, projectId = projectId, @@ -1144,8 +1098,7 @@ class PipelineInfoFacadeService @Autowired constructor( description = description, yaml = yaml, baseVersion = baseVersion, - yamlInfo = yamlInfo, - pipelineDialect = pipelineDialect + yamlInfo = yamlInfo ) // 审计 ActionAuditContext.current() @@ -1216,7 +1169,7 @@ class PipelineInfoFacadeService @Autowired constructor( userId: String, projectId: String, pipelineId: String, - buildNo: BuildNoUpdateReq + targetBuildNo: Int ) { operationLogService.addOperationLog( userId = userId, @@ -1224,14 +1177,14 @@ class PipelineInfoFacadeService @Autowired constructor( pipelineId = pipelineId, version = 0, operationLogType = OperationLogType.RESET_RECOMMENDED_VERSION_BUILD_NO, - params = buildNo.currentBuildNo.toString(), + params = targetBuildNo.toString(), description = null ) pipelineBuildSummaryDao.updateBuildNo( dslContext = dslContext, projectId = projectId, pipelineId = pipelineId, - buildNo = buildNo.currentBuildNo, + buildNo = targetBuildNo, debug = false ) } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineVersionFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineVersionFacadeService.kt index 39ab5330a15f..0c7426da0629 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineVersionFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/PipelineVersionFacadeService.kt @@ -27,6 +27,7 @@ package com.tencent.devops.process.service +import com.tencent.devops.common.api.check.Preconditions import com.tencent.devops.common.api.constant.CommonMessageCode import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.model.SQLLimit @@ -78,11 +79,11 @@ import com.tencent.devops.process.template.service.TemplateService import com.tencent.devops.process.utils.PipelineVersionUtils import com.tencent.devops.process.yaml.PipelineYamlFacadeService import com.tencent.devops.process.yaml.transfer.PipelineTransferException +import javax.ws.rs.core.Response import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import javax.ws.rs.core.Response @Suppress("ALL") @Service @@ -174,6 +175,7 @@ class PipelineVersionFacadeService @Autowired constructor( VersionStatus.COMMITTING -> { draftVersion?.version } + VersionStatus.BRANCH -> { val branchVersion = pipelineRepositoryService.getBranchVersionResource( projectId, pipelineId, null @@ -181,6 +183,7 @@ class PipelineVersionFacadeService @Autowired constructor( versionName = branchVersion?.versionName branchVersion?.version } + else -> { draftVersion?.version } @@ -541,7 +544,8 @@ class PipelineVersionFacadeService @Autowired constructor( id = "T-1-1-1", name = I18nUtil.getCodeLanMessage( CommonMessageCode.BK_MANUAL_TRIGGER, - language = I18nUtil.getLanguage(userId + language = I18nUtil.getLanguage( + userId ) ) ) @@ -560,6 +564,20 @@ class PipelineVersionFacadeService @Autowired constructor( version = request.templateVersion ).template } + val pipelineAsCodeSettings = PipelineAsCodeSettings.initDialect( + inheritedDialect = request.inheritedDialect, + pipelineDialect = request.pipelineDialect + ) + val setting = pipelineRepositoryService.createDefaultSetting( + projectId = projectId, + pipelineId = "", + pipelineName = request.pipelineName, + channelCode = ChannelCode.BS + ).copy( + pipelineAsCodeSettings = pipelineAsCodeSettings, + labels = request.labels + ) + return pipelineInfoFacadeService.createPipeline( userId = userId, projectId = projectId, @@ -567,17 +585,16 @@ class PipelineVersionFacadeService @Autowired constructor( name = request.pipelineName, templateId = request.templateId, instanceFromTemplate = false, - staticViews = request.staticViews + staticViews = request.staticViews, + labels = request.labels ), channelCode = ChannelCode.BS, + setting = setting, checkPermission = true, instanceType = request.instanceType, versionStatus = VersionStatus.COMMITTING, useSubscriptionSettings = request.useSubscriptionSettings, - useLabelSettings = request.useLabelSettings, - useConcurrencyGroup = request.useConcurrencyGroup, - inheritedDialectSetting = request.inheritedDialect, - pipelineDialectSetting = request.pipelineDialect + useConcurrencyGroup = request.useConcurrencyGroup ) } @@ -634,12 +651,14 @@ class PipelineVersionFacadeService @Autowired constructor( ) Triple(true, response, null) } catch (e: PipelineTransferException) { - Triple(false, null, I18nUtil.getCodeLanMessage( - messageCode = e.errorCode, - params = e.params, - language = I18nUtil.getLanguage(I18nUtil.getRequestUserId()), - defaultMessage = e.defaultMessage - )) + Triple( + false, null, I18nUtil.getCodeLanMessage( + messageCode = e.errorCode, + params = e.params, + language = I18nUtil.getLanguage(I18nUtil.getRequestUserId()), + defaultMessage = e.defaultMessage + ) + ) } return PipelineVersionWithModel( modelAndSetting = modelAndSetting, @@ -726,16 +745,28 @@ class PipelineVersionFacadeService @Autowired constructor( logger.warn("TRANSFER_YAML|$projectId|$userId|${ignore.message}|modelAndYaml=\n${modelAndYaml.yaml}") newYaml = null } - model = modelAndYaml.modelAndSetting.model - setting = modelAndYaml.modelAndSetting.setting + model = Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.model, + "modelAndYaml.modelAndSetting.model must not be null" + ) + setting = Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.setting, + "modelAndYaml.modelAndSetting.setting must not be null" + ) } return if (pipelineId.isNullOrBlank()) { // 新建流水线产生草稿 pipelineInfoFacadeService.createPipeline( userId = userId, projectId = projectId, - model = model ?: modelAndYaml.modelAndSetting.model, - setting = setting ?: modelAndYaml.modelAndSetting.setting, + model = model ?: Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.model, + "The transfer data is incorrect, so the modelAndYaml.modelAndSetting.model must not be null" + ), + setting = setting ?: Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.setting, + "The transfer data is incorrect, so the modelAndYaml.modelAndSetting.setting must not be null" + ), channelCode = ChannelCode.BS, checkPermission = true, versionStatus = versionStatus, @@ -749,7 +780,10 @@ class PipelineVersionFacadeService @Autowired constructor( userId = userId, projectId = projectId, pipelineId = pipelineId, - setting = setting ?: modelAndYaml.modelAndSetting.setting, + setting = setting ?: Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.setting, + "The transfer data is incorrect, so the modelAndYaml.modelAndSetting.setting must not be null" + ), checkPermission = false, versionStatus = versionStatus, dispatchPipelineUpdateEvent = false, @@ -763,7 +797,10 @@ class PipelineVersionFacadeService @Autowired constructor( release?.model } else { model - } ?: modelAndYaml.modelAndSetting.model, + } ?: Preconditions.checkNotNull( + modelAndYaml.modelAndSetting?.model, + "The transfer data is incorrect, so the modelAndYaml.modelAndSetting.model must not be null" + ), channelCode = ChannelCode.BS, checkPermission = true, checkTemplate = false, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStartUpService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStartUpService.kt index 0e61fcc0cef3..677b20f0b18d 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStartUpService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/SubPipelineStartUpService.kt @@ -259,10 +259,6 @@ class SubPipelineStartUpService @Autowired constructor( val model = resource.model val triggerContainer = model.getTriggerContainer() - templateFacadeService.printModifiedTemplateParams( - projectId = projectId, pipelineId = pipelineId, - pipelineParams = triggerContainer.params, paramValues = parameters - ) // #6090 拨乱反正 val params = buildParamCompatibilityTransformer.parseTriggerParam( userId = userId, projectId = projectId, pipelineId = pipelineId, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt index d0d7172c1245..189a71059ea1 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt @@ -255,15 +255,23 @@ class PipelineBuildFacadeService( val realValue = latestParamsMap[param.id] // 入参、推荐版本号参数有上一次的构建参数的时候才设置成默认值,否者依然使用默认值 // 当值是boolean类型的时候,需要转为boolean类型 - param.value = if (param.constant == true) { - param.readOnly = true - param.defaultValue - } else if (!param.required && !recommendVersionKey(param.id)) { - param.defaultValue - } else if (param.defaultValue is Boolean) { - realValue?.toString()?.toBoolean() - } else { - realValue + param.value = when { + param.constant == true -> { + param.readOnly = true + param.defaultValue + } + + !param.required && !recommendVersionKey(param.id) -> { + param.defaultValue + } + + param.defaultValue is Boolean -> { + realValue?.toString()?.toBoolean() + } + + else -> { + realValue + } } ?: param.defaultValue } } else { @@ -664,10 +672,6 @@ class PipelineBuildFacadeService( logger.info("[$pipelineId] buildNo was changed to [$buildNo]") } - templateFacadeService.printModifiedTemplateParams( - projectId = projectId, pipelineId = pipelineId, - pipelineParams = triggerContainer.params, paramValues = values - ) val paramMap = buildParamCompatibilityTransformer.parseTriggerParam( userId = userId, projectId = projectId, pipelineId = pipelineId, paramProperties = triggerContainer.params, paramValues = values diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt index e1e5fca0e24a..33bd412f0ab9 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/template/TemplateFacadeService.kt @@ -1526,11 +1526,13 @@ class TemplateFacadeService @Autowired constructor( logger.info("[$userId|$projectId|$templateId|$version] Get the param ($instanceParams)") // 模板中的buildNo存在才需要回显 + // 将实例自己维护的当前值一起返回 val instanceBuildNoObj = templateTriggerContainer.buildNo?.let { no -> BuildNo( buildNoType = no.buildNoType, required = no.required ?: instanceTriggerContainer.buildNo?.required, - buildNo = buildNos[pipelineId] ?: no.buildNo + buildNo = no.buildNo, + currentBuildNo = buildNos[pipelineId] ) } @@ -1538,7 +1540,10 @@ class TemplateFacadeService @Autowired constructor( pipelineId = pipelineId, pipelineName = getPipelineName(settings, pipelineId) ?: templateModel.name, buildNo = instanceBuildNoObj, - param = instanceParams + param = instanceParams, + updateBuildNo = instanceTriggerContainer.buildNo?.let { ino -> + ino.buildNo != templateModel.getTriggerContainer().buildNo?.buildNo + } ) }.toMap() } catch (ignored: Throwable) { @@ -1871,6 +1876,16 @@ class TemplateFacadeService @Autowired constructor( checkPermission = true, checkTemplate = false ) + templateInstanceUpdate.buildNo?.let { + if (templateInstanceUpdate.resetBuildNo == true) { + pipelineInfoFacadeService.updateBuildNo( + userId = userId, + projectId = projectId, + pipelineId = templateInstanceUpdate.pipelineId, + targetBuildNo = it.buildNo + ) + } + } } } @@ -2622,37 +2637,6 @@ class TemplateFacadeService @Autowired constructor( return pipelineTemplatePermissionService.enableTemplatePermissionManage(projectId) } - // TODO 埋点统计模板常量在流水线启动时被修改日志, 后续需要删除 - fun printModifiedTemplateParams( - projectId: String, - pipelineId: String, - pipelineParams: List, - paramValues: Map - ) { - val templatePipelineRecord = templatePipelineDao.get(dslContext, projectId, pipelineId) ?: return - val templateRecord = - templateDao.getTemplate(dslContext = dslContext, version = templatePipelineRecord.version) ?: return - val template: Model = objectMapper.readValue(templateRecord.template) - val templateParams = (template.getTriggerContainer()).templateParams - if (templateParams.isNullOrEmpty()) { - return - } - pipelineParams.forEach { param -> - val value = paramValues[param.id] ?: param.defaultValue - templateParams.forEach { template -> - if (template.id == param.id && template.defaultValue != value) { - logger.warn( - "BKSystemErrorMonitor|$projectId|$pipelineId|" + - "templateId:${templateRecord.id}|templateVersion:${templateRecord.version}|" + - "defaultValue:${template.defaultValue}|newValue:$value|" + - "template params cannot be modified" - ) - return - } - } - } - } - companion object { private val logger = LoggerFactory.getLogger(TemplateFacadeService::class.java) private const val INIT_TEMPLATE_NAME = "init" diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/view/PipelineViewService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/view/PipelineViewService.kt index ba48490f0798..6d912725f958 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/view/PipelineViewService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/view/PipelineViewService.kt @@ -516,7 +516,8 @@ class PipelineViewService @Autowired constructor( logger.warn("duplicate name , project:$projectId , user:$userId , view:$pipelineView") throw ErrorCodeException( errorCode = ProcessMessageCode.ERROR_VIEW_DUPLICATE_NAME, - defaultMessage = "view name is duplicate , name:${pipelineView.name}" + defaultMessage = "view name is duplicate , name:${pipelineView.name}", + params = arrayOf(pipelineView.name) ) } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt index b122030cd269..a386ab3fc512 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt @@ -29,7 +29,9 @@ package com.tencent.devops.process.service.webhook import com.tencent.devops.common.api.enums.RepositoryType import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.exception.PermissionForbiddenException import com.tencent.devops.common.api.util.JsonUtil +import com.tencent.devops.common.auth.api.AuthPermission import com.tencent.devops.common.client.Client import com.tencent.devops.common.event.dispatcher.SampleEventDispatcher import com.tencent.devops.common.event.pojo.measure.ProjectUserDailyEvent @@ -44,6 +46,7 @@ import com.tencent.devops.common.pipeline.pojo.BuildParameters import com.tencent.devops.common.pipeline.pojo.element.trigger.WebHookTriggerElement import com.tencent.devops.common.pipeline.utils.PIPELINE_PAC_REPO_HASH_ID import com.tencent.devops.common.service.prometheus.BkTimed +import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.common.webhook.pojo.code.PIPELINE_START_WEBHOOK_USER_ID import com.tencent.devops.common.webhook.service.code.loader.WebhookElementParamsRegistrar import com.tencent.devops.common.webhook.service.code.loader.WebhookStartParamsRegistrar @@ -57,11 +60,13 @@ import com.tencent.devops.process.engine.service.PipelineWebHookQueueService import com.tencent.devops.process.engine.service.PipelineWebhookService import com.tencent.devops.process.engine.service.WebhookBuildParameterService import com.tencent.devops.process.engine.service.code.GitWebhookUnlockDispatcher +import com.tencent.devops.process.permission.PipelinePermissionService import com.tencent.devops.process.pojo.BuildId import com.tencent.devops.process.pojo.code.WebhookBuildResult import com.tencent.devops.process.pojo.code.WebhookCommit import com.tencent.devops.process.pojo.trigger.PipelineTriggerDetailBuilder import com.tencent.devops.process.pojo.trigger.PipelineTriggerEvent +import com.tencent.devops.process.pojo.trigger.PipelineTriggerFailedErrorCode import com.tencent.devops.process.pojo.trigger.PipelineTriggerFailedMatch import com.tencent.devops.process.pojo.trigger.PipelineTriggerFailedMatchElement import com.tencent.devops.process.pojo.trigger.PipelineTriggerFailedMsg @@ -73,7 +78,6 @@ import com.tencent.devops.process.service.pipeline.PipelineBuildService import com.tencent.devops.process.trigger.PipelineTriggerEventService import com.tencent.devops.process.utils.PIPELINE_START_TASK_ID import com.tencent.devops.process.utils.PipelineVarUtil -import com.tencent.devops.process.webhook.PipelineBuildPermissionService import com.tencent.devops.process.yaml.PipelineYamlService import com.tencent.devops.repository.api.ServiceRepositoryResource import org.slf4j.LoggerFactory @@ -96,8 +100,8 @@ class PipelineBuildWebhookService @Autowired constructor( private val webhookBuildParameterService: WebhookBuildParameterService, private val pipelineTriggerEventService: PipelineTriggerEventService, private val measureEventDispatcher: SampleEventDispatcher, - private val pipelineBuildPermissionService: PipelineBuildPermissionService, - private val pipelineYamlService: PipelineYamlService + private val pipelineYamlService: PipelineYamlService, + private val pipelinePermissionService: PipelinePermissionService ) { companion object { private val logger = LoggerFactory.getLogger(PipelineBuildWebhookService::class.java) @@ -268,6 +272,11 @@ class PipelineBuildWebhookService @Autowired constructor( val matchResult = matcher.isMatch(projectId, pipelineId, repo, webHookParams) if (matchResult.isMatch) { try { + checkPermission( + userId = userId, + projectId = projectId, + pipelineId = pipelineId + ) val webhookCommit = WebhookCommit( userId = userId, pipelineId = pipelineId, @@ -312,6 +321,19 @@ class PipelineBuildWebhookService @Autowired constructor( .reason(PipelineTriggerReason.TRIGGER_SUCCESS.name) .buildNum(buildDetail?.buildNum.toString()) } + } catch (permissionException: PermissionForbiddenException) { + logger.warn("check permission failed", permissionException) + builder.eventSource(repo.repoHashId!!) + .status(PipelineTriggerStatus.FAILED.name) + .reason(PipelineTriggerReason.TRIGGER_FAILED.name) + .reasonDetail( + PipelineTriggerFailedErrorCode( + errorCode = ProcessMessageCode.BK_AUTHOR_NOT_PIPELINE_EXECUTE_PERMISSION, + params = listOf(userId) + ) + ) + // 当前流水线没有权限触发 + return false } catch (ignore: Exception) { logger.warn("$pipelineId|webhook trigger|(${element.name})|repo(${matcher.getRepoName()})", ignore) builder.eventSource(eventSource = repo.repoHashId!!) @@ -513,7 +535,6 @@ class PipelineBuildWebhookService @Autowired constructor( // errorCode = ProcessMessageCode.ERROR_NO_RELEASE_PIPELINE_VERSION // ) val version = webhookCommit.version ?: pipelineInfo.version - checkPermission(pipelineInfo.lastModifyUser, projectId = projectId, pipelineId = pipelineId) val resource = pipelineRepositoryService.getPipelineResourceVersion( projectId = projectId, @@ -600,7 +621,16 @@ class PipelineBuildWebhookService @Autowired constructor( } private fun checkPermission(userId: String, projectId: String, pipelineId: String) { - pipelineBuildPermissionService.checkPermission(userId = userId, projectId = projectId, pipelineId = pipelineId) + pipelinePermissionService.validPipelinePermission( + userId = userId, + projectId = projectId, + pipelineId = pipelineId, + permission = AuthPermission.EXECUTE, + message = I18nUtil.getCodeLanMessage( + messageCode = ProcessMessageCode.USER_NO_PIPELINE_PERMISSION_UNDER_PROJECT, + params = arrayOf(userId, projectId, AuthPermission.EXECUTE.getI18n(I18nUtil.getLanguage(userId))) + ) + ) } private fun uploadProjectUserMetrics( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlFacadeService.kt index aea246c6a9cc..2082072f6753 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlFacadeService.kt @@ -116,40 +116,49 @@ class PipelineYamlFacadeService @Autowired constructor( logger.warn("enable pac,not found ci yaml from git|$projectId|$repoHashId") return } - // 创建yaml流水线组 - pipelineYamlViewService.createYamlViewIfAbsent( - userId = action.data.getUserId(), - projectId = projectId, - repoHashId = repoHashId, - gitProjectName = action.data.setting.projectName, - directoryList = yamlPathList.map { GitActionCommon.getCiDirectory(it.yamlPath) }.toSet() - ) - val path2PipelineExists = pipelineYamlInfoDao.getAllByRepo( - dslContext = dslContext, projectId = projectId, repoHashId = repoHashId - ).associate { - it.filePath to YamlTriggerPipeline( - projectId = it.projectId, - repoHashId = it.repoHashId, - filePath = it.filePath, - pipelineId = it.pipelineId, - userId = userId + try { + // 创建yaml流水线组 + pipelineYamlViewService.createYamlViewIfAbsent( + userId = action.data.getUserId(), + projectId = projectId, + repoHashId = repoHashId, + gitProjectName = action.data.setting.projectName, + directoryList = yamlPathList.map { GitActionCommon.getCiDirectory(it.yamlPath) }.toSet() ) - } - yamlPathList.forEach { - action.data.context.pipeline = path2PipelineExists[it.yamlPath] - action.data.context.yamlFile = it - pipelineEventDispatcher.dispatch( - PipelineYamlEnableEvent( - projectId = projectId, - yamlPath = it.yamlPath, - userId = userId, - eventStr = objectMapper.writeValueAsString(event), - metaData = action.metaData, - actionCommonData = action.data.eventCommon, - actionContext = action.data.context, - actionSetting = action.data.setting + val path2PipelineExists = pipelineYamlInfoDao.getAllByRepo( + dslContext = dslContext, projectId = projectId, repoHashId = repoHashId + ).associate { + it.filePath to YamlTriggerPipeline( + projectId = it.projectId, + repoHashId = it.repoHashId, + filePath = it.filePath, + pipelineId = it.pipelineId, + userId = userId + ) + } + yamlPathList.forEach { + action.data.context.pipeline = path2PipelineExists[it.yamlPath] + action.data.context.yamlFile = it + pipelineEventDispatcher.dispatch( + PipelineYamlEnableEvent( + projectId = projectId, + yamlPath = it.yamlPath, + userId = userId, + eventStr = objectMapper.writeValueAsString(event), + metaData = action.metaData, + actionCommonData = action.data.eventCommon, + actionContext = action.data.context, + actionSetting = action.data.setting + ) ) + } + } catch (exception: Exception) { + logger.error("Failed to enable pac|projectId:$projectId|repoHashId:$repoHashId", exception) + pipelineYamlSyncService.enablePacFailed( + projectId = projectId, + repoHashId = repoHashId ) + throw exception } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlRepositoryService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlRepositoryService.kt index 503d7b2153bc..2376c20ada3e 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlRepositoryService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlRepositoryService.kt @@ -40,6 +40,7 @@ import com.tencent.devops.common.pipeline.utils.RepositoryConfigUtils import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.process.engine.service.PipelineRepositoryService import com.tencent.devops.process.engine.service.PipelineWebhookService +import com.tencent.devops.process.pojo.pipeline.PipelineYamlView import com.tencent.devops.process.pojo.pipeline.PipelineYamlVo import com.tencent.devops.process.pojo.pipeline.enums.PipelineYamlStatus import com.tencent.devops.process.pojo.webhook.PipelineWebhookVersion @@ -625,16 +626,10 @@ class PipelineYamlRepositoryService @Autowired constructor( // 删除流水线组 val yamlViews = pipelineYamlViewService.listRepoYamlView(projectId = projectId, repoHashId = repoHashId) yamlViews.forEach { yamlView -> - pipelineViewGroupService.deleteViewGroup( + deleteYamlView( projectId = projectId, userId = userId, - viewIdEncode = HashUtil.encodeLongId(yamlView.viewId), - checkPac = false - ) - pipelineYamlViewService.deleteYamlView( - projectId = projectId, - repoHashId = repoHashId, - directory = yamlView.directory + yamlView = yamlView ) } // 删除yaml同步记录 @@ -671,6 +666,27 @@ class PipelineYamlRepositoryService @Autowired constructor( filePath = filePath ) if (refreshView) { + // 如果PAC流水线组已经没有流水线了,那么就将这个流水线组删除 + val directory = GitActionCommon.getCiDirectory(filePath) + val yamlPipelineCnt = pipelineYamlService.countPipelineYaml( + projectId = projectId, + repoHashId = repoHashId, + directory = directory + ) + if (yamlPipelineCnt == 0L) { + logger.info("delete pipeline yaml view|$projectId|$repoHashId|$directory") + pipelineYamlViewService.getPipelineYamlView( + projectId = projectId, + repoHashId = repoHashId, + directory = directory + )?.let { + deleteYamlView( + projectId = projectId, + userId = userId, + yamlView = it + ) + } + } val pipelineInfo = pipelineRepositoryService.getPipelineInfo(projectId, pipelineId) ?: return pipelineViewGroupService.updateGroupAfterPipelineUpdate( projectId = projectId, @@ -682,6 +698,24 @@ class PipelineYamlRepositoryService @Autowired constructor( } } + private fun deleteYamlView( + projectId: String, + userId: String, + yamlView: PipelineYamlView + ) { + pipelineViewGroupService.deleteViewGroup( + projectId = projectId, + userId = userId, + viewIdEncode = HashUtil.encodeLongId(yamlView.viewId), + checkPac = false + ) + pipelineYamlViewService.deleteYamlView( + projectId = projectId, + repoHashId = yamlView.repoHashId, + directory = yamlView.directory + ) + } + /** * TODO 需优化 * 本来应该在com.tencent.devops.process.engine.service.PipelineWebhookService.addWebhook处理, diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlService.kt index 668c21e01463..8cb7a2e2e18a 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlService.kt @@ -31,7 +31,6 @@ package com.tencent.devops.process.yaml import com.tencent.devops.common.api.enums.RepositoryType import com.tencent.devops.common.api.model.SQLPage import com.tencent.devops.common.client.Client -import com.tencent.devops.model.process.tables.records.TPipelineYamlBranchFileRecord import com.tencent.devops.process.engine.dao.PipelineInfoDao import com.tencent.devops.process.engine.dao.PipelineWebhookVersionDao import com.tencent.devops.process.engine.dao.PipelineYamlBranchFileDao @@ -299,17 +298,6 @@ class PipelineYamlService( } } - fun listEnablePacPipelineMap( - projectId: String, - pipelineIds: List - ): List { - return pipelineYamlInfoDao.listByPipelineIds( - dslContext = dslContext, - projectId = projectId, - pipelineIds = pipelineIds - ) - } - /** * 获取当前分支或blob_id对应的最新的版本 */ @@ -410,12 +398,14 @@ class PipelineYamlService( fun countPipelineYaml( projectId: String, - repoHashId: String + repoHashId: String, + directory: String? = null ): Long { return pipelineYamlInfoDao.countYamlPipeline( dslContext = dslContext, projectId = projectId, - repoHashId = repoHashId + repoHashId = repoHashId, + directory = directory ) } @@ -514,19 +504,4 @@ class PipelineYamlService( branch = branch ) } - - fun getBranchFilePath( - projectId: String, - repoHashId: String, - branch: String, - filePath: String - ): TPipelineYamlBranchFileRecord? { - return pipelineYamlBranchFileDao.get( - dslContext = dslContext, - projectId = projectId, - repoHashId = repoHashId, - branch = branch, - filePath = filePath - ) - } } diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlSyncService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlSyncService.kt index 16a91d8205ea..d8c800986cd7 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlSyncService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/yaml/PipelineYamlSyncService.kt @@ -89,6 +89,17 @@ class PipelineYamlSyncService @Autowired constructor( ) } + fun enablePacFailed( + projectId: String, + repoHashId: String + ) { + client.get(ServiceRepositoryPacResource::class).updateYamlSyncStatus( + projectId = projectId, + repoHashId = repoHashId, + syncStatus = RepoYamlSyncStatusEnum.FAILED.name + ) + } + /** * 同步成功 */ diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectConstant.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectConstant.kt index 5233dda0f4f4..e4ff8b79624f 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectConstant.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectConstant.kt @@ -31,4 +31,6 @@ object ProjectConstant { const val PROJECT_NAME_MAX_LENGTH = 64 const val PROJECT_ID_MAX_LENGTH = 32 const val NAME_MIN_LENGTH = 2 + // 流水线命名规范 + const val PIPELINE_NAME_FORMAT_MAX_LENGTH = 200 } diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectMessageCode.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectMessageCode.kt index 02624f431b6f..d91f8f1cf8d8 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectMessageCode.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/constant/ProjectMessageCode.kt @@ -100,6 +100,7 @@ object ProjectMessageCode { const val ERROR_PROJECT_NOT_RELATED_PRODUCT = "2119052" // 项目未关联产品ID const val ERROR_PRODUCT_NOT_EXIST = "2119053" // 运营产品不存在 const val ERROR_ORGANIZATION_CAN_NOT_TO_BE_EMPTY = "2119054" // 组织架构不允许为空 + const val ERROR_PIPELINE_NAME_FORMAT_TOO_LONG = "2119055" // 流水线命名规范不能超过200个字符 const val BK_CONTAINER_SERVICE = "bkContainerService" // 容器服务 const val BK_FAILED_BSC_CREATE_PROJECT = "bkFailedBscCreateProject" // 调用BSC接口创建项目失败 diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectByConditionDTO.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectByConditionDTO.kt index fefb3a0d0bb1..1df6099eb71a 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectByConditionDTO.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectByConditionDTO.kt @@ -13,5 +13,7 @@ data class ProjectByConditionDTO( @get:Schema(title = "环境路由") val routerTag: String?, @get:Schema(title = "bgId") - val bgId: Long? = null + val bgId: Long? = null, + @get:Schema(title = "remotedev管理员") + val remotedevManager: String? = null ) diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectDiffVO.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectDiffVO.kt index 1096d030adea..a148cabf2b15 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectDiffVO.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectDiffVO.kt @@ -124,5 +124,13 @@ data class ProjectDiffVO( @get:Schema(title = "流水线语言风格") val pipelineDialect: String? = null, @get:Schema(title = "审批中流水线语言风格") - val afterPipelineDialect: String? = null + val afterPipelineDialect: String? = null, + @get:Schema(title = "是否开启流水线命名提示") + val enablePipelineNameTips: Boolean? = false, + @get:Schema(title = "审批中是否开启流水线命名提示") + val afterEnablePipelineNameTips: Boolean? = false, + @get:Schema(title = "流水线命名格式") + val pipelineNameFormat: String? = null, + @get:Schema(title = "审批中流水线命名格式") + val afterPipelineNameFormat: String? = null ) diff --git a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt index 95f524fe9146..708a79012316 100644 --- a/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt +++ b/src/backend/ci/core/project/api-project/src/main/kotlin/com/tencent/devops/project/pojo/ProjectProperties.kt @@ -60,5 +60,22 @@ data class ProjectProperties( PluginDetailsDisplayOrder.CONFIG ), @get:Schema(title = "流水线语法风格") - var pipelineDialect: String? = "CLASSIC" -) + var pipelineDialect: String? = "CLASSIC", + @get:Schema(title = "是否开启流水线命名提示") + var enablePipelineNameTips: Boolean? = false, + @get:Schema(title = "流水线命名格式") + var pipelineNameFormat: String? = null +) { + /** + * 接受前端请求时,只复制前端展示修改的值,由op控制的值不能修改 + */ + fun userCopy(updateProperties: ProjectProperties): ProjectProperties { + with(updateProperties) { + return this@ProjectProperties.copy( + pipelineDialect = pipelineDialect, + enablePipelineNameTips = enablePipelineNameTips, + pipelineNameFormat = pipelineNameFormat + ) + } + } +} diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/dao/ProjectDao.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/dao/ProjectDao.kt index 75b337443c89..4a1d18dbca1d 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/dao/ProjectDao.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/dao/ProjectDao.kt @@ -196,6 +196,9 @@ class ProjectDao { ) } } + if (queryRemoteDevFlag == true) { + conditions.add(JooqUtils.jsonExtractAny(PROPERTIES, "\$.remotedev").isTrue) + } } } return conditions diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/ServiceProjectResourceImpl.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/ServiceProjectResourceImpl.kt index b04b607d3c4e..bc6a440756c7 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/ServiceProjectResourceImpl.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/resources/ServiceProjectResourceImpl.kt @@ -28,6 +28,7 @@ package com.tencent.devops.project.resources import com.tencent.bk.audit.annotations.AuditEntry +import com.tencent.devops.common.api.exception.ParamBlankException import com.tencent.devops.common.auth.api.ActionId import com.tencent.devops.common.auth.api.ActionId.PROJECT_CREATE import com.tencent.devops.common.auth.api.AuthPermission @@ -37,6 +38,7 @@ import com.tencent.devops.common.web.RestResource import com.tencent.devops.project.api.service.ServiceProjectResource import com.tencent.devops.project.pojo.OrgInfo import com.tencent.devops.project.pojo.ProjectBaseInfo +import com.tencent.devops.project.pojo.ProjectByConditionDTO import com.tencent.devops.project.pojo.ProjectCreateExtInfo import com.tencent.devops.project.pojo.ProjectCreateInfo import com.tencent.devops.project.pojo.ProjectCreateUserInfo @@ -44,7 +46,6 @@ import com.tencent.devops.project.pojo.ProjectOrganizationInfo import com.tencent.devops.project.pojo.ProjectProperties import com.tencent.devops.project.pojo.ProjectUpdateInfo import com.tencent.devops.project.pojo.ProjectVO -import com.tencent.devops.project.pojo.ProjectByConditionDTO import com.tencent.devops.project.pojo.Result import com.tencent.devops.project.pojo.enums.PluginDetailsDisplayOrder import com.tencent.devops.project.pojo.enums.ProjectChannelCode @@ -257,6 +258,9 @@ class ServiceProjectResourceImpl @Autowired constructor( projectCode: String, projectOrganizationInfo: ProjectOrganizationInfo ): Result { + if (projectOrganizationInfo.bgId == null || projectOrganizationInfo.bgName.isNullOrBlank()) { + throw ParamBlankException("bgId or bgName must be not null.") + } projectService.updateOrganizationByEnglishName( englishName = projectCode, projectOrganizationInfo = projectOrganizationInfo diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectApprovalService.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectApprovalService.kt index 0bf229b60c9b..7de1a78d1ca9 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectApprovalService.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/ProjectApprovalService.kt @@ -260,13 +260,7 @@ class ProjectApprovalService @Autowired constructor( ) // 属性只能变更前端展示的,其他的字段由op变更 val projectProperties = projectInfo.properties?.let { JsonUtil.to(it, ProjectProperties::class.java) } - val updateProjectProperties = if (projectApprovalInfo.properties != null) { - projectProperties?.copy( - pipelineDialect = projectApprovalInfo.properties!!.pipelineDialect - ) - } else { - projectProperties - } + val updateProjectProperties = projectApprovalInfo.properties?.let { projectProperties?.userCopy(it) } val projectUpdateInfo = with(projectApprovalInfo) { ProjectUpdateInfo( projectName = projectName, diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt index cda63255184a..31bed594fb25 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/service/impl/AbsProjectServiceImpl.kt @@ -70,6 +70,7 @@ import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.model.project.tables.records.TProjectRecord import com.tencent.devops.project.SECRECY_PROJECT_REDIS_KEY import com.tencent.devops.project.constant.ProjectConstant.NAME_MIN_LENGTH +import com.tencent.devops.project.constant.ProjectConstant.PIPELINE_NAME_FORMAT_MAX_LENGTH import com.tencent.devops.project.constant.ProjectConstant.PROJECT_ID_MAX_LENGTH import com.tencent.devops.project.constant.ProjectConstant.PROJECT_NAME_MAX_LENGTH import com.tencent.devops.project.constant.ProjectMessageCode @@ -357,6 +358,7 @@ abstract class AbsProjectServiceImpl @Autowired constructor( deptId = deptId, deptName = deptName ) + validateProperties(properties) } } @@ -554,10 +556,7 @@ abstract class AbsProjectServiceImpl @Autowired constructor( // 属性只能变更前端展示的,其他的字段由op变更 val properties = projectInfo.properties?.let { JsonUtil.to(it, ProjectProperties::class.java) } ?: ProjectProperties() - if (projectUpdateInfo.properties != null) { - projectUpdateInfo.properties = - properties.copy(pipelineDialect = projectUpdateInfo.properties!!.pipelineDialect) - } + projectUpdateInfo.properties = projectUpdateInfo.properties?.let { properties.userCopy(it) } // 判断是否需要审批,当修改最大授权范围/权限敏感/关联运营产品时需要审批 val (finalNeedApproval, newApprovalStatus) = getUpdateApprovalStatus( needApproval = needApproval, @@ -694,6 +693,7 @@ abstract class AbsProjectServiceImpl @Autowired constructor( deptId = deptId, deptName = deptName ) + validateProperties(properties) } } @@ -1044,6 +1044,7 @@ abstract class AbsProjectServiceImpl @Autowired constructor( limit: Int, offset: Int ): List { + logger.info("list projects by condition:$projectConditionDTO|$limit|$offset") return projectDao.listProjectsByCondition( dslContext = dslContext, projectConditionDTO = projectConditionDTO, @@ -1055,7 +1056,12 @@ abstract class AbsProjectServiceImpl @Autowired constructor( englishName = it.englishName, permission = true, routerTag = buildRouterTag(it.routerTag), - bgId = it.bgId + bgId = it.bgId, + remotedevManager = it.properties?.let { properties -> + JsonUtil.to( + properties, ProjectProperties::class.java + ) + }?.remotedevManager ) } } @@ -1658,6 +1664,17 @@ abstract class AbsProjectServiceImpl @Autowired constructor( ?: PipelineDialectType.CLASSIC.name } + private fun validateProperties(properties: ProjectProperties?) { + properties?.pipelineNameFormat?.let { + if (it.length > PIPELINE_NAME_FORMAT_MAX_LENGTH) { + throw ErrorCodeException( + errorCode = ProjectMessageCode.ERROR_PIPELINE_NAME_FORMAT_TOO_LONG, + defaultMessage = "The naming convention for pipelines should not exceed 200 characters." + ) + } + } + } + companion object { const val MAX_PROJECT_NAME_LENGTH = 64 private val logger = LoggerFactory.getLogger(AbsProjectServiceImpl::class.java)!! diff --git a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/util/ProjectUtils.kt b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/util/ProjectUtils.kt index 3fa1e431c431..f3f75ea61b0c 100644 --- a/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/util/ProjectUtils.kt +++ b/src/backend/ci/core/project/biz-project/src/main/kotlin/com/tencent/devops/project/util/ProjectUtils.kt @@ -196,7 +196,11 @@ object ProjectUtils { productName = beforeProductName, afterProductName = projectApprovalInfo?.productName, pipelineDialect = projectProperties?.pipelineDialect, - afterPipelineDialect = projectApprovalProperties?.pipelineDialect + afterPipelineDialect = projectApprovalProperties?.pipelineDialect, + enablePipelineNameTips = projectProperties?.enablePipelineNameTips, + afterEnablePipelineNameTips = projectApprovalProperties?.enablePipelineNameTips, + pipelineNameFormat = projectProperties?.pipelineNameFormat, + afterPipelineNameFormat = projectApprovalProperties?.pipelineNameFormat ) } } diff --git a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt index 878379d72905..a6b5950add2d 100644 --- a/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt +++ b/src/backend/ci/core/quality/biz-quality/src/main/kotlin/com/tencent/devops/quality/dao/v2/QualityMetadataDao.kt @@ -28,6 +28,7 @@ package com.tencent.devops.quality.dao.v2 import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.common.db.utils.skipCheck import com.tencent.devops.model.quality.tables.TQualityMetadata import com.tencent.devops.model.quality.tables.records.TQualityMetadataRecord import com.tencent.devops.quality.api.v2.pojo.op.QualityMetaData @@ -65,6 +66,7 @@ class QualityMetadataDao { return with(TQualityMetadata.T_QUALITY_METADATA) { dslContext.selectFrom(this) .where(ELEMENT_TYPE.eq(elementType)) + .skipCheck() .fetch() } } diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt index 6a287bde3b27..94de8eebe03f 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceGithubResource.kt @@ -69,7 +69,10 @@ interface ServiceGithubResource { tokenType: String, @Parameter(description = "accessToken范围", required = true) @QueryParam("scope") - scope: String + scope: String, + @Parameter(description = "蓝盾平台操作人", required = false) + @QueryParam("operator") + operator: String? ): Result @Operation(summary = "获取github代码库accessToken") diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt index dfd44d69a8d3..fff5b5240b80 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/ServiceOauthResource.kt @@ -123,4 +123,13 @@ interface ServiceOauthResource { @QueryParam("refreshToken") refreshToken: Boolean? = false ): Result + + @Operation(summary = "根据用户ID判断用户是否已经github oauth认证") + @GET + @Path("/github_oauth") + fun githubOAuth( + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String + ): Result } diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt new file mode 100644 index 000000000000..eb7280c11ab6 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/UserOauthResource.kt @@ -0,0 +1,108 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + * + */ + +package com.tencent.devops.repository.api + +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "AUTH_RESOURCE", description = "用户态-iam资源映射") +@Path("/user/repositories/oauth/") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface UserOauthResource { + @GET + @Path("/") + @Operation(summary = "获取用户OAuth授权列表") + fun list( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String + ): Result> + + @GET + @Path("/relSource") + @Operation(summary = "获取授权关联的资源列表") + fun relSource( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String, + @Parameter(description = "第几页", required = false, example = "1") + @QueryParam("page") + page: Int? = null, + @Parameter(description = "每页多少条", required = false, example = "20") + @QueryParam("pageSize") + pageSize: Int? = null + ): Result> + + @DELETE + @Path("/delete") + @Operation(summary = "删除oauth授权") + fun delete( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String + ): Result + + @POST + @Path("/reset") + @Operation(summary = "重置授权") + fun reset( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "授权类型", required = true) + @QueryParam("scmCode") + scmCode: String, + @Parameter(description = "回调链接(授权完以后的链接地址)", required = true) + @QueryParam("redirectUrl") + redirectUrl: String + ): Result +} diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt index a34084593e0d..da2e959434a3 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/github/ServiceGithubUserResource.kt @@ -47,7 +47,7 @@ import javax.ws.rs.core.MediaType @Consumes(MediaType.APPLICATION_JSON) interface ServiceGithubUserResource { - @Operation(summary = "创建或者更新文件内容") + @Operation(summary = "获取用户信息") @GET @Path("/getUser") fun getUser( diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/scm/ServiceGitResource.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/scm/ServiceGitResource.kt index c74d8f88ea03..7faa655092e4 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/scm/ServiceGitResource.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/api/scm/ServiceGitResource.kt @@ -411,6 +411,9 @@ interface ServiceGitResource { @Parameter(description = "将项目名作为目录打包进去 (默认:false)", required = false) @QueryParam("isProjectPathWrapped") isProjectPathWrapped: Boolean?, + @Parameter(description = "项目ID", required = false) + @QueryParam("projectId") + projectId: String?, @Context response: HttpServletResponse ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryConstants.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryConstants.kt index d111edca4754..fd399d1046c1 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryConstants.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryConstants.kt @@ -30,4 +30,5 @@ package com.tencent.devops.repository.constant object RepositoryConstants { const val CI_DIR_PATH = ".ci" + const val KEY_REPOSITORY_ID = "repositoryId" } diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt index a0f561300a15..35425ce90596 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/constant/RepositoryMessageCode.kt @@ -94,8 +94,10 @@ object RepositoryMessageCode { const val ERROR_USER_HAVE_NOT_DOWNLOAD_PEM = "2115043" // 用户({0})无({1})项目下载权限 const val NOT_GITHUB_AUTHORIZED_BY_OAUTH = "2115044" // 用户[{0}]尚未进行GITHUB OAUTH授权,请先授权。 const val REPOSITORY_NO_SUPPORT_OAUTH = "2115045" // ({0})类型代码库暂不支持OAUTH授权 + const val OAUTH_INFO_OCCUPIED_CANNOT_DELETE = "2115049" // OAUTH授权信息被占用,无法删除 const val USER_NOT_PERMISSIONS_OPERATE_REPOSITORY = "2115046" // 用户({0})无权限在工程({1})下{2}流水线{3} + const val FAIL_TO_GET_OPEN_COPILOT_TOKEN = "2115048" // 获取open copilot token 失败, 失败详情: {0} const val BK_REQUEST_FILE_SIZE_LIMIT = "bkRequestFileSizeLimit" // 请求文件不能超过1M const val OPERATION_ADD_CHECK_RUNS = "OperationAddCheckRuns" // 添加检测任务 diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt index 5490ee419cbf..4eeafb6eff6b 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/CodeGitRepository.kt @@ -61,6 +61,8 @@ data class CodeGitRepository( ) : Repository { companion object { const val classType = "codeGit" + // 内部工蜂 + const val SCM_CODE = "TGIT" } override fun getStartPrefix(): String { diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt index b2d86a038da8..e8cf984b1c48 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/GithubRepository.kt @@ -56,6 +56,7 @@ data class GithubRepository( ) : Repository { companion object { const val classType = "github" + const val SCM_CODE = "GITHUB" } override fun getStartPrefix() = "https://github.com/" diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt new file mode 100644 index 000000000000..b2f39bb691ed --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/OauthResetUrl.kt @@ -0,0 +1,8 @@ +package com.tencent.devops.repository.pojo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "重置Oauth授权信息") +data class OauthResetUrl( + val url: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt new file mode 100644 index 000000000000..3a778e518198 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepoOauthRefVo.kt @@ -0,0 +1,15 @@ +package com.tencent.devops.repository.pojo + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户关联的仓库") +data class RepoOauthRefVo( + @get:Schema(title = "仓库别名", required = true) + val aliasName: String, + @get:Schema(title = "仓库源URL", required = true) + val url: String, + @get:Schema(title = "蓝盾项目ID", required = true) + val projectId: String, + @get:Schema(title = "代码库HashId", required = true) + val hashId: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepositoryInfo.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepositoryInfo.kt index 05cb2b24fc33..962da1d8873b 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepositoryInfo.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/RepositoryInfo.kt @@ -47,5 +47,7 @@ data class RepositoryInfo( @get:Schema(title = "最后更新时间", required = true) val updatedTime: Long, @get:Schema(title = "创建人", required = false) - val createUser: String? = null + val createUser: String? = null, + @get:Schema(title = "远程仓库ID", required = false) + val remoteRepoId: Long? = null ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt new file mode 100644 index 000000000000..465a2acf2988 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/UserOauthRepositoryInfo.kt @@ -0,0 +1,26 @@ +package com.tencent.devops.repository.pojo + +import com.tencent.devops.common.api.enums.ScmType +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "用户代码库Oauth授权信息") +data class UserOauthRepositoryInfo( + @get:Schema(title = "授权账号") + val username: String, + @get:Schema(title = "授权代码库数量") + val repoCount: Long, + @get:Schema(title = "创建时间") + val createTime: Long? = null, + @get:Schema(title = "授权类型") + val scmCode: String, + @get:Schema(title = "是否过期") + val expired: Boolean = false, + @get:Schema(title = "是否已授权") + val authorized: Boolean = true, + @get:Schema(title = "操作人") + val operator: String, + @get:Schema(title = "操作人") + val scmType: ScmType, + @get:Schema(title = "代码库类型") + val name: String +) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/enums/TokenAppTypeEnum.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/enums/TokenAppTypeEnum.kt new file mode 100644 index 000000000000..28e31c9fd03c --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/enums/TokenAppTypeEnum.kt @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.pojo.enums + +/** + * token 应用类型 + */ +enum class TokenAppTypeEnum { + OAUTH2, // oauth2认证 + COPILOT_OPEN_TOKEN; // copilot open token +} diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt index 013d7f3d900c..b73662790c03 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/github/GithubToken.kt @@ -39,5 +39,11 @@ data class GithubToken( @get:Schema(title = "token类型", description = "token_type") val tokenType: String, @get:Schema(title = "范围") - val scope: String + val scope: String, + @get:Schema(title = "创建时间(时间戳)") + val createTime: Long, + @get:Schema(title = "用户名 (github server端的用户名)") + val userId: String? = "", + @get:Schema(title = "操作者 (蓝盾平台用户名)") + val operator: String? = "" ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt index 05e4a34432f2..90d0097eb2ff 100644 --- a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/GitToken.kt @@ -45,5 +45,9 @@ data class GitToken( @JsonProperty("expires_in") val expiresIn: Long = 0L, @get:Schema(title = "创建时间") - val createTime: Long? = 0L + val createTime: Long? = 0L, + @get:Schema(title = "更新时间") + val updateTime: Long? = 0L, + @get:Schema(title = "操作人") + var operator: String? = "" ) diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt new file mode 100644 index 000000000000..fdce6d41bf22 --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/Oauth2AccessToken.kt @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ +package com.tencent.devops.repository.pojo.oauth + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "Oauth2 Access Token") +data class Oauth2AccessToken( + val accessToken: String, + val tokenType: String, + val expiresIn: Long? = 0L, + val refreshToken: String? = null, + val createTime: Long? = 0L, + @get:Schema(title = "server端用户名") + val userId: String? = null, + @get:Schema(title = "操作者") + val operator: String? = null +) \ No newline at end of file diff --git a/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/RepositoryScmToken.kt b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/RepositoryScmToken.kt new file mode 100644 index 000000000000..b0984dfcc41c --- /dev/null +++ b/src/backend/ci/core/repository/api-repository/src/main/kotlin/com/tencent/devops/repository/pojo/oauth/RepositoryScmToken.kt @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.pojo.oauth + +import com.fasterxml.jackson.annotation.JsonProperty +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "Token模型") +data class RepositoryScmToken( + @get:Schema(title = "鉴权token") + @JsonProperty("access_token") + var userId: String = "", + @get:Schema(title = "代码库类型") + var scmCode: String = "", + @get:Schema(title = "应用类型") + var appType: String = "", + @get:Schema(title = "鉴权token") + var accessToken: String = "", + @get:Schema(title = "刷新token") + var refreshToken: String = "", + @get:Schema(title = "过期时间") + val expiresIn: Long = 0L, + @get:Schema(title = "创建时间") + val createTime: Long? = 0L +) diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt index 2376f6f7ae6a..e5939070e25f 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GitTokenDao.kt @@ -45,6 +45,7 @@ class GitTokenDao { } fun saveAccessToken(dslContext: DSLContext, userId: String, token: GitToken): Int { + val now = LocalDateTime.now() with(TRepositoryGitToken.T_REPOSITORY_GIT_TOKEN) { return dslContext.insertInto( this, @@ -53,7 +54,9 @@ class GitTokenDao { REFRESH_TOKEN, TOKEN_TYPE, EXPIRES_IN, - CREATE_TIME + CREATE_TIME, + UPDATE_TIME, + OPERATOR ) .values( userId, @@ -61,14 +64,23 @@ class GitTokenDao { token.refreshToken, token.tokenType, token.expiresIn, - LocalDateTime.now() + now, + now, + token.operator ?: userId ) .onDuplicateKeyUpdate() .set(ACCESS_TOKEN, token.accessToken) .set(REFRESH_TOKEN, token.refreshToken) .set(TOKEN_TYPE, token.tokenType) .set(EXPIRES_IN, token.expiresIn) - .set(CREATE_TIME, LocalDateTime.now()) + .set(UPDATE_TIME, now) + .let { + // 避免空值覆盖操作人 + if (!token.operator.isNullOrBlank()) { + it.set(OPERATOR, token.operator) + } + it + } .execute() } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt index e016c2c5f778..434842dfd84e 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/GithubTokenDao.kt @@ -42,7 +42,8 @@ class GithubTokenDao { accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { val now = LocalDateTime.now() with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { @@ -54,7 +55,8 @@ class GithubTokenDao { SCOPE, CREATE_TIME, UPDATE_TIME, - TYPE + TYPE, + OPERATOR ).values( userId, accessToken, @@ -62,7 +64,8 @@ class GithubTokenDao { scope, now, now, - githubTokenType.name + githubTokenType.name, + operator ).execute() } } @@ -73,13 +76,20 @@ class GithubTokenDao { accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { dslContext.update(this) .set(TOKEN_TYPE, tokenType) .set(ACCESS_TOKEN, accessToken) .set(SCOPE, scope) + .let { + if (operator.isNotBlank()) { + it.set(OPERATOR, operator) + } + it + } .where(USER_ID.eq(userId)).and(TYPE.eq(githubTokenType.name)) .execute() } @@ -91,7 +101,11 @@ class GithubTokenDao { githubTokenType: GithubTokenType? ): TRepositoryGithubTokenRecord? { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { - return dslContext.selectFrom(this).where(USER_ID.eq(userId)) + return getByOperator( + dslContext = dslContext, + operator = userId, + githubTokenType = githubTokenType + ) ?: dslContext.selectFrom(this).where(USER_ID.eq(userId)) .let { if (githubTokenType != null) { it.and(TYPE.eq(githubTokenType.name)) @@ -101,12 +115,35 @@ class GithubTokenDao { } } + fun getByOperator( + dslContext: DSLContext, + operator: String, + githubTokenType: GithubTokenType? + ): TRepositoryGithubTokenRecord? { + with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { + return dslContext.selectFrom(this) + .where(OPERATOR.eq(operator)) + .let { + if (githubTokenType != null) { + it.and(TYPE.eq(githubTokenType.name)) + } else it + } + .orderBy(CREATE_TIME.desc()) + .fetch() + .firstOrNull() + } + } + + /** + * 删除token + * @param operator 蓝盾平台操作人用户名 + */ fun delete( dslContext: DSLContext, - userId: String + operator: String ) { with(TRepositoryGithubToken.T_REPOSITORY_GITHUB_TOKEN) { - dslContext.deleteFrom(this).where(USER_ID.eq(userId)) + dslContext.deleteFrom(this).where(OPERATOR.eq(operator)) } } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt index 58cae5fe1be7..ab1071111e91 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryCodeGitDao.kt @@ -27,8 +27,11 @@ package com.tencent.devops.repository.dao +import com.tencent.devops.common.api.enums.ScmType +import com.tencent.devops.model.repository.tables.TRepository import com.tencent.devops.model.repository.tables.TRepositoryCodeGit import com.tencent.devops.model.repository.tables.records.TRepositoryCodeGitRecord +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.UpdateRepositoryInfoRequest import com.tencent.devops.repository.pojo.enums.RepoAuthType import org.jooq.DSLContext @@ -188,4 +191,58 @@ class RepositoryCodeGitDao { .fetch() } } + + fun countOauthRepo( + dslContext: DSLContext, + userId: String + ): Long { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryCodeGit.T_REPOSITORY_CODE_GIT + return dslContext.selectCount() + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.CODE_GIT.name), + t2.AUTH_TYPE.eq(RepoAuthType.OAUTH.name), + t2.USER_NAME.eq(userId) + ) + ) + .fetchOne(0, Long::class.java)!! + } + + fun listOauthRepo( + dslContext: DSLContext, + userId: String, + limit: Int, + offset: Int + ): List { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryCodeGit.T_REPOSITORY_CODE_GIT + return dslContext.select(t1.ALIAS_NAME, t1.URL, t1.PROJECT_ID, t1.REPOSITORY_HASH_ID) + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.CODE_GIT.name), + t2.AUTH_TYPE.eq(RepoAuthType.OAUTH.name), + t2.USER_NAME.eq(userId) + ) + ) + .limit(limit) + .offset(offset) + .fetch() + .map { + RepoOauthRefVo( + aliasName = it.get(0).toString(), + url = it.get(1).toString(), + projectId = it.get(2).toString(), + hashId = it.get(3).toString() + ) + } + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt index 015e79bb4be4..034d3a7522f4 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryGithubDao.kt @@ -27,9 +27,12 @@ package com.tencent.devops.repository.dao +import com.tencent.devops.common.api.enums.ScmType +import com.tencent.devops.model.repository.tables.TRepository import com.tencent.devops.model.repository.tables.TRepositoryCodeGitlab import com.tencent.devops.model.repository.tables.TRepositoryGithub import com.tencent.devops.model.repository.tables.records.TRepositoryGithubRecord +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.UpdateRepositoryInfoRequest import org.jooq.DSLContext import org.jooq.Result @@ -162,4 +165,56 @@ class RepositoryGithubDao { .execute() } } + + fun countOauthRepo( + dslContext: DSLContext, + userId: String + ): Long { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryGithub.T_REPOSITORY_GITHUB + return dslContext.selectCount() + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.GITHUB.name), + t2.USER_NAME.eq(userId) + ) + ) + .fetchOne(0, Long::class.java)!! + } + + fun listOauthRepo( + dslContext: DSLContext, + userId: String, + limit: Int, + offset: Int + ): List { + val t1 = TRepository.T_REPOSITORY + val t2 = TRepositoryGithub.T_REPOSITORY_GITHUB + return dslContext.select(t1.ALIAS_NAME, t1.URL, t1.PROJECT_ID, t1.REPOSITORY_HASH_ID) + .from(t1) + .leftJoin(t2) + .on(t1.REPOSITORY_ID.eq(t2.REPOSITORY_ID)) + .where( + listOf( + t1.IS_DELETED.eq(false), + t1.TYPE.eq(ScmType.GITHUB.name), + t2.USER_NAME.eq(userId) + ) + ) + .limit(limit) + .offset(offset) + .fetch() + .map { + RepoOauthRefVo( + aliasName = it.get(0).toString(), + url = it.get(1).toString(), + projectId = it.get(2).toString(), + hashId = it.get(3).toString() + ) + } + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryScmTokenDao.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryScmTokenDao.kt new file mode 100644 index 000000000000..e17e9b2df050 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/dao/RepositoryScmTokenDao.kt @@ -0,0 +1,88 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.dao + +import com.tencent.devops.model.repository.tables.TRepositoryScmToken +import com.tencent.devops.model.repository.tables.records.TRepositoryScmTokenRecord +import com.tencent.devops.repository.pojo.oauth.RepositoryScmToken +import org.jooq.DSLContext +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class RepositoryScmTokenDao { + fun getToken( + dslContext: DSLContext, + userId: String, + scmCode: String, + appType: String + ): TRepositoryScmTokenRecord? { + with(TRepositoryScmToken.T_REPOSITORY_SCM_TOKEN) { + return dslContext.selectFrom(this) + .where( + USER_ID.eq(userId) + .and(SCM_CODE.eq(scmCode)) + .and(APP_TYPE.eq(appType)) + ) + .fetchOne() + } + } + + fun saveAccessToken(dslContext: DSLContext, scmToken: RepositoryScmToken): Int { + with(TRepositoryScmToken.T_REPOSITORY_SCM_TOKEN) { + val now = LocalDateTime.now() + return dslContext.insertInto( + this, + USER_ID, + SCM_CODE, + APP_TYPE, + ACCESS_TOKEN, + REFRESH_TOKEN, + EXPIRES_IN, + CREATE_TIME, + UPDATE_TIME + ) + .values( + scmToken.userId, + scmToken.scmCode, + scmToken.appType, + scmToken.accessToken, + scmToken.refreshToken, + scmToken.expiresIn, + now, + now + ) + .onDuplicateKeyUpdate() + .set(ACCESS_TOKEN, scmToken.accessToken) + .set(REFRESH_TOKEN, scmToken.refreshToken) + .set(EXPIRES_IN, scmToken.expiresIn) + .set(UPDATE_TIME, LocalDateTime.now()) + .execute() + } + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt index 0c8beaf07399..bd0f4251c18f 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceGithubResourceImpl.kt @@ -52,9 +52,10 @@ class ServiceGithubResourceImpl @Autowired constructor( userId: String, accessToken: String, tokenType: String, - scope: String + scope: String, + operator: String? ): Result { - githubTokenService.createAccessToken(userId, accessToken, tokenType, scope) + githubTokenService.createAccessToken(userId, accessToken, tokenType, scope, operator = operator ?: userId) return Result(true) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt index 6b6b08fa600e..a87ef8deb281 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/ServiceOauthResourceImpl.kt @@ -34,6 +34,7 @@ import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.oauth.GitOauthCallback import com.tencent.devops.repository.pojo.oauth.GitToken +import com.tencent.devops.repository.service.github.IGithubService import com.tencent.devops.repository.service.scm.IGitOauthService import com.tencent.devops.repository.service.tgit.TGitOAuthService import org.springframework.beans.factory.annotation.Autowired @@ -41,7 +42,8 @@ import org.springframework.beans.factory.annotation.Autowired @RestResource class ServiceOauthResourceImpl @Autowired constructor( private val gitOauthService: IGitOauthService, - private val tGitOAuthService: TGitOAuthService + private val tGitOAuthService: TGitOAuthService, + private val githubService: IGithubService ) : ServiceOauthResource { override fun gitGet(userId: String): Result { return Result(gitOauthService.getAccessToken(userId)) @@ -90,4 +92,19 @@ class ServiceOauthResourceImpl @Autowired constructor( ) ) } + + override fun githubOAuth( + userId: String + ): Result { + return Result( + githubService.isOAuth( + userId = userId, + redirectUrl = "", + redirectUrlType = null, + projectId = "", + resetType = "", + refreshToken = false + ) + ) + } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt new file mode 100644 index 000000000000..88339cdc5049 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/UserOauthResourceImpl.kt @@ -0,0 +1,63 @@ +package com.tencent.devops.repository.resources + +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.repository.api.UserOauthResource +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.service.OauthRepositoryService +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class UserOauthResourceImpl @Autowired constructor( + val oauthRepositoryService: OauthRepositoryService +) : UserOauthResource { + override fun list(userId: String): Result> { + return Result(oauthRepositoryService.list(userId)) + } + + override fun relSource( + userId: String, + scmCode: String, + page: Int?, + pageSize: Int? + ): Result> { + val targetPage = page ?: 1 + val targetPageSize = pageSize ?: 20 + val resources = oauthRepositoryService.relSource( + userId = userId, + scmCode = ScmCode.valueOf(scmCode), + page = targetPage, + pageSize = targetPageSize + ) + return Result(resources) + } + + override fun delete( + userId: String, + scmCode: String + ): Result { + oauthRepositoryService.delete( + userId = userId, + scmCode = ScmCode.valueOf(scmCode) + ) + return Result(true) + } + + override fun reset( + userId: String, + scmCode: String, + redirectUrl: String + ): Result { + return Result( + oauthRepositoryService.reset( + userId = userId, + scmCode = ScmCode.valueOf(scmCode), + redirectUrl = redirectUrl + ) + ) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/scm/ServiceGitResourceImpl.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/scm/ServiceGitResourceImpl.kt index 5384d92ac29f..6ab12342d738 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/scm/ServiceGitResourceImpl.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/resources/scm/ServiceGitResourceImpl.kt @@ -279,10 +279,11 @@ class ServiceGitResourceImpl @Autowired constructor( filePath: String?, format: String?, isProjectPathWrapped: Boolean?, + projectId: String?, response: HttpServletResponse ) { val repo = repositoryService.serviceGet( - "", + projectId ?: "", RepositoryConfigUtils.buildConfig(repoId, repositoryType) ) repoFileService.downloadTGitRepoFile( diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt new file mode 100644 index 000000000000..3eb3c6e848c1 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/OauthRepositoryService.kt @@ -0,0 +1,135 @@ +package com.tencent.devops.repository.service + +import com.tencent.devops.repository.pojo.OauthResetUrl +import com.tencent.devops.repository.pojo.UserOauthRepositoryInfo +import com.tencent.devops.common.api.enums.ScmCode +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.pojo.Page +import com.tencent.devops.common.api.util.PageUtil +import com.tencent.devops.repository.constant.RepositoryMessageCode +import com.tencent.devops.repository.pojo.RepoOauthRefVo +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum +import com.tencent.devops.repository.service.github.GithubOAuthService +import com.tencent.devops.repository.service.oauth2.Oauth2TokenStoreManager +import com.tencent.devops.repository.service.scm.IGitOauthService +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +/** + * OAUTH授权代码库服务类 + */ +@Service +class OauthRepositoryService @Autowired constructor( + val oauth2TokenStoreManager: Oauth2TokenStoreManager, + val gitOauthService: IGitOauthService, + val githubOAuthService: GithubOAuthService, + val repositoryService: RepositoryService +) { + fun list(userId: String): List { + val list = mutableListOf() + listOf( + ScmCode.TGIT, + ScmCode.GITHUB + ).forEach { scmCode -> + val userOauthInfo = oauth2TokenStoreManager.get( + userId = userId, + scmCode = scmCode.name + )?.let { + UserOauthRepositoryInfo( + username = it.userId ?: "", + repoCount = repositoryService.countOauthRepo( + userId = userId, + scmType = scmCode.convertScmType() + ), + scmCode = scmCode.value, + scmType = scmCode.convertScmType(), + name = scmCode.scmName, + operator = it.operator ?: it.userId ?: "", + createTime = it.createTime, + expired = it.expiresIn?.let { expiresIn -> + (it.createTime ?: 0L) + expiresIn * 1000 <= System.currentTimeMillis() + } ?: false, + authorized = true + ) + } ?: UserOauthRepositoryInfo( + username = userId, + repoCount = 0L, + scmCode = scmCode.value, + expired = true, + authorized = false, + scmType = scmCode.convertScmType(), + name = scmCode.scmName, + operator = userId + ) + list.add(userOauthInfo) + } + return list + } + + fun relSource( + userId: String, + scmCode: ScmCode, + page: Int, + pageSize: Int + ): Page { + val limit = PageUtil.convertPageSizeToSQLLimit(page, pageSize) + val result = repositoryService.listOauthRepo( + userId = userId, + scmType = scmCode.convertScmType(), + limit = limit.limit, + offset = limit.offset + ) + return Page(page, pageSize, result.count, result.records) + } + + fun delete( + userId: String, + scmCode: ScmCode + ) { + // 检查是否还有关联代码库 + if (countOauthRepo(userId, scmCode) > 0) { + throw ErrorCodeException( + errorCode = RepositoryMessageCode.OAUTH_INFO_OCCUPIED_CANNOT_DELETE + ) + } + oauth2TokenStoreManager.delete(userId = userId, scmCode = scmCode.name) + } + + fun countOauthRepo(userId: String, scmCode: ScmCode) = repositoryService.countOauthRepo( + userId = userId, + scmType = scmCode.convertScmType() + ) + + fun reset( + userId: String, + scmCode: ScmCode, + redirectUrl: String + ): OauthResetUrl { + val url = when (scmCode) { + ScmCode.TGIT -> { + gitOauthService.getOauthUrl( + userId = userId, + redirectUrl = redirectUrl + ) + } + + ScmCode.GITHUB -> { + githubOAuthService.getGithubOauth( + userId = userId, + projectId = "", + redirectUrlTypeEnum = RedirectUrlTypeEnum.SPEC, + specRedirectUrl = redirectUrl, + repoHashId = null + ).redirectUrl + } + + else -> "" + } + return OauthResetUrl(url) + } + + companion object { + val logger = LoggerFactory.getLogger(OauthRepositoryService::class.java) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepoFileService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepoFileService.kt index 9f8ca8e028b1..3ee0de20cd69 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepoFileService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepoFileService.kt @@ -101,9 +101,10 @@ class RepoFileService @Autowired constructor( reversion: String?, branch: String?, subModule: String? = null, - svnFullPath: Boolean = false + svnFullPath: Boolean = false, + projectId: String = "" ): String { - val repo = repositoryService.serviceGet("", repositoryConfig) + val repo = repositoryService.serviceGet(projectId, repositoryConfig) return getFileContent( repo = repo, filePath = filePath, @@ -401,9 +402,10 @@ class RepoFileService @Autowired constructor( fun updateTGitFileContent( repositoryConfig: RepositoryConfig, userId: String, - gitOperationFile: GitOperationFile + gitOperationFile: GitOperationFile, + projectId: String = "" ): Result { - val repo = repositoryService.serviceGet("", repositoryConfig) + val repo = repositoryService.serviceGet(projectId, repositoryConfig) return updateTGitSingleFile( repoUrl = repo.url, repoName = repo.projectName, diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt index 0d5ae9b7177a..d69d780b489e 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryAuthService.kt @@ -153,7 +153,7 @@ class RepositoryAuthService @Autowired constructor( repositoryInfos.map { val entity = ResourceAuthorizationResponse( projectCode = projectId, - resourceType = AuthResourceType.PIPELINE_DEFAULT.value, + resourceType = AuthResourceType.CODE_REPERTORY.value, resourceName = it.aliasName, resourceCode = it.repositoryHashId!!, handoverTime = it.updatedTime, diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt index a273036b64f2..2ae8f6558b5f 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/RepositoryService.kt @@ -67,10 +67,12 @@ import com.tencent.devops.repository.constant.RepositoryMessageCode.REPOSITORY_N import com.tencent.devops.repository.constant.RepositoryMessageCode.USER_CREATE_PEM_ERROR import com.tencent.devops.repository.dao.RepositoryCodeGitDao import com.tencent.devops.repository.dao.RepositoryDao +import com.tencent.devops.repository.dao.RepositoryGithubDao import com.tencent.devops.repository.pojo.AtomRefRepositoryInfo import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.CodeGitRepository import com.tencent.devops.repository.pojo.GithubRepository +import com.tencent.devops.repository.pojo.RepoOauthRefVo import com.tencent.devops.repository.pojo.RepoRename import com.tencent.devops.repository.pojo.Repository import com.tencent.devops.repository.pojo.RepositoryDetailInfo @@ -117,7 +119,8 @@ class RepositoryService @Autowired constructor( private val dslContext: DSLContext, private val repositoryPermissionService: RepositoryPermissionService, private val githubService: IGithubService, - private val client: Client + private val client: Client, + private val repositoryGithubDao: RepositoryGithubDao ) { @Value("\${repository.git.devopsPrivateToken}") @@ -209,7 +212,8 @@ class RepositoryService @Autowired constructor( aliasName = gitRepositoryResp.name, url = gitRepositoryResp.repositoryUrl, type = ScmType.CODE_GIT, - updatedTime = LocalDateTime.now().timestampmilli() + updatedTime = LocalDateTime.now().timestampmilli(), + remoteRepoId = gitRepositoryResp.id ) ) } else { @@ -1560,6 +1564,62 @@ class RepositoryService @Autowired constructor( ) } + fun listOauthRepo( + userId: String, + scmType: ScmType, + limit: Int, + offset: Int + ): SQLPage { + val list = when (scmType) { + ScmType.CODE_GIT -> { + repositoryCodeGitDao.listOauthRepo( + dslContext = dslContext, + userId = userId, + limit = limit, + offset = offset + ) + } + + ScmType.GITHUB -> { + repositoryGithubDao.listOauthRepo( + dslContext = dslContext, + userId = userId, + limit = limit, + offset = offset + ) + } + + else -> { + listOf() + } + } + val count = countOauthRepo(userId, scmType) + return SQLPage(count, list) + } + + fun countOauthRepo( + userId: String, + scmType: ScmType + ): Long { + return when (scmType) { + ScmType.CODE_GIT -> { + repositoryCodeGitDao.countOauthRepo( + dslContext = dslContext, + userId = userId + ) + } + + ScmType.GITHUB -> { + repositoryGithubDao.countOauthRepo( + dslContext = dslContext, + userId = userId + ) + } + + else -> 0L + } + } + companion object { private val logger = LoggerFactory.getLogger(RepositoryService::class.java) const val MAX_ALIAS_LENGTH = 255 diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGitRepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGitRepositoryService.kt index a8a6744a5e83..43d07463504d 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGitRepositoryService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGitRepositoryService.kt @@ -206,25 +206,19 @@ class CodeGitRepositoryService @Autowired constructor( authType = repository.authType, gitProjectId = gitProjectId ) - val repositoryCodeRecord = repositoryCodeGitDao.get( - dslContext = transactionContext, - repositoryId = repositoryId - ) - if (repositoryCodeRecord.authType == RepoAuthType.OAUTH.name && - repositoryCodeRecord.userName != repository.userName) { - repositoryAuthorizationService.batchModifyHandoverFrom( - projectId = projectId, - resourceAuthorizationHandoverList = listOf( - ResourceAuthorizationHandoverDTO( - projectCode = projectId, - resourceType = AuthResourceType.CODE_REPERTORY.value, - resourceName = record.aliasName, - resourceCode = repositoryHashId, - handoverTo = repository.userName - ) + // 重置授权管理 + repositoryAuthorizationService.batchModifyHandoverFrom( + projectId = projectId, + resourceAuthorizationHandoverList = listOf( + ResourceAuthorizationHandoverDTO( + projectCode = projectId, + resourceType = AuthResourceType.CODE_REPERTORY.value, + resourceName = record.aliasName, + resourceCode = repositoryHashId, + handoverTo = repository.userName ) ) - } + ) } } @@ -336,11 +330,21 @@ class CodeGitRepositoryService @Autowired constructor( } override fun pacCheckEnabled(projectId: String, userId: String, record: TRepositoryRecord, retry: Boolean) { - val codeGitRepository = compose(record) - if (codeGitRepository.authType != RepoAuthType.OAUTH) { + val repository = compose(record) + if (repository.authType != RepoAuthType.OAUTH) { throw ErrorCodeException(errorCode = ERROR_AUTH_TYPE_ENABLED_PAC) } - pacCheckEnabled(projectId = projectId, userId = userId, repository = codeGitRepository, retry = retry) + val gitProjectId = + pacCheckEnabled(projectId = projectId, userId = userId, repository = repository, retry = retry) + // 修复历史数据 + if (repository.gitProjectId == null || repository.gitProjectId == 0L) { + val repositoryId = HashUtil.decodeOtherIdToLong(repository.repoHashId!!) + repositoryCodeGitDao.updateGitProjectId( + dslContext = dslContext, + id = repositoryId, + gitProjectId = gitProjectId + ) + } } private fun pacCheckEnabled( @@ -348,7 +352,7 @@ class CodeGitRepositoryService @Autowired constructor( userId: String, repository: CodeGitRepository, retry: Boolean - ) { + ): Long { if (repository.authType != RepoAuthType.OAUTH) { throw ErrorCodeException(errorCode = ERROR_AUTH_TYPE_ENABLED_PAC) } @@ -418,15 +422,7 @@ class CodeGitRepositoryService @Autowired constructor( userName = userId, event = CodeGitWebhookEvent.MERGE_REQUESTS_EVENTS.value ) - // 修复历史数据 - if (repository.gitProjectId == null || repository.gitProjectId == 0L) { - val repositoryId = HashUtil.decodeOtherIdToLong(repository.repoHashId!!) - repositoryCodeGitDao.updateGitProjectId( - dslContext = dslContext, - id = repositoryId, - gitProjectId = gitProjectInfo.id - ) - } + return gitProjectInfo.id } override fun getGitFileTree(projectId: String, userId: String, record: TRepositoryRecord): List { diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGithubRepositoryService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGithubRepositoryService.kt index a92d7e01ab9a..f11daa50b82d 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGithubRepositoryService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/code/CodeGithubRepositoryService.kt @@ -151,24 +151,18 @@ class CodeGithubRepositoryService @Autowired constructor( repository.userName, gitProjectId = gitProjectId ) - val githubRepositoryRecord = repositoryGithubDao.get( - dslContext = transactionContext, - repositoryId = repositoryId - ) - if (githubRepositoryRecord.userName != repository.userName) { - repositoryAuthorizationService.batchModifyHandoverFrom( - projectId = projectId, - resourceAuthorizationHandoverList = listOf( - ResourceAuthorizationHandoverDTO( - projectCode = projectId, - resourceType = AuthResourceType.CODE_REPERTORY.value, - resourceName = record.aliasName, - resourceCode = repositoryHashId, - handoverTo = repository.userName - ) + repositoryAuthorizationService.batchModifyHandoverFrom( + projectId = projectId, + resourceAuthorizationHandoverList = listOf( + ResourceAuthorizationHandoverDTO( + projectCode = projectId, + resourceType = AuthResourceType.CODE_REPERTORY.value, + resourceName = record.aliasName, + resourceCode = repositoryHashId, + handoverTo = repository.userName ) ) - } + ) } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt index c9a94ddfa308..252d875ac103 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubOAuthService.kt @@ -41,6 +41,7 @@ import com.tencent.devops.repository.pojo.github.GithubOauth import com.tencent.devops.repository.pojo.github.GithubOauthCallback import com.tencent.devops.repository.pojo.github.GithubToken import com.tencent.devops.repository.pojo.oauth.GithubTokenType +import com.tencent.devops.repository.sdk.github.response.GetUserResponse import com.tencent.devops.repository.sdk.github.service.GithubUserService import com.tencent.devops.repository.service.ScmUrlProxyService import com.tencent.devops.scm.config.GitConfig @@ -51,6 +52,7 @@ import org.apache.commons.lang3.RandomStringUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.net.URLDecoder import java.net.URLEncoder import javax.ws.rs.core.Response @@ -85,8 +87,9 @@ class GithubOAuthService @Autowired constructor( specRedirectUrl, redirectUrlTypeEnum?.type ).joinToString(separator = OAUTH_URL_STATE_SEPARATOR) + val encodeState = URLEncoder.encode(state, "UTF-8") val redirectUrl = "$GITHUB_URL/login/oauth/authorize" + - "?client_id=${gitConfig.githubClientId}&redirect_uri=${gitConfig.githubCallbackUrl}&state=$state" + "?client_id=${gitConfig.githubClientId}&redirect_uri=${gitConfig.githubCallbackUrl}&state=$encodeState" return GithubOauth(redirectUrl) } @@ -135,10 +138,11 @@ class GithubOAuthService @Autowired constructor( if (state.isNullOrBlank() || !state.contains(",BK_DEVOPS__")) { throw OperationException("TGIT call back contain invalid parameter: $state") } + val sourceState = URLDecoder.decode(state, "UTF-8") // 回调状态信息 // @see com.tencent.devops.repository.service.github.GithubOAuthService.getGithubOauth // 格式:{{授权用户Id}},{{蓝盾项目Id}},{{蓝盾代码库Id}},{{回调标识}},{{弹框标识位}},{{重置类型}},{{跳转链接}},{{跳转类型}} - val arrays = state.split(OAUTH_URL_STATE_SEPARATOR) + val arrays = sourceState.split(OAUTH_URL_STATE_SEPARATOR) val userId = arrays[0] val projectId = arrays[1] val repoHashId = if (arrays[2].isNotBlank()) HashUtil.encodeOtherLongId(arrays[2].toLong()) else "" @@ -151,12 +155,15 @@ class GithubOAuthService @Autowired constructor( val specRedirectUrl = arrays.getOrNull(6) ?: "" // 重定向类型 val redirectUrlTypeEnum = RedirectUrlTypeEnum.getRedirectUrlType(arrays.getOrNull(7) ?: "") + // 获取授权server端用户信息(蓝盾平台用户名可能跟github用户名不一致) + val userResponse = getUser(githubToken.accessToken) githubTokenService.createAccessToken( - userId = userId, + userId = userResponse.login, accessToken = githubToken.accessToken, tokenType = githubToken.tokenType, scope = githubToken.scope, - githubTokenType = githubTokenType + githubTokenType = githubTokenType, + operator = userId ) return GithubOauthCallback( userId = userId, @@ -179,11 +186,12 @@ class GithubOAuthService @Autowired constructor( val userResponse = githubUserService.getUser(githubToken.accessToken) val stateMap = kotlin.runCatching { JsonUtil.toMap(state ?: "{}") }.getOrDefault(emptyMap()) githubTokenService.createAccessToken( - userId = stateMap["userId"]?.toString() ?: userResponse.login, + userId = userResponse.login, accessToken = githubToken.accessToken, tokenType = githubToken.tokenType, scope = githubToken.scope, - githubTokenType = githubTokenType + githubTokenType = githubTokenType, + operator = stateMap["userId"]?.toString() ?: userResponse.login ) return GithubOauthCallback( userId = userResponse.login, @@ -227,6 +235,10 @@ class GithubOAuthService @Autowired constructor( } } + fun getUser(accessToken: String): GetUserResponse { + return githubUserService.getUser(accessToken) + } + companion object { private val logger = LoggerFactory.getLogger(GithubOAuthService::class.java) private const val RANDOM_ALPHA_NUM = 8 diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt index 0d7d662b5010..b304b719e5dd 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubService.kt @@ -53,6 +53,7 @@ import com.tencent.devops.repository.constant.RepositoryMessageCode.OPERATION_UP import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.GithubCheckRuns import com.tencent.devops.repository.pojo.GithubCheckRunsResponse +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.github.GithubBranch import com.tencent.devops.repository.pojo.github.GithubRepo import com.tencent.devops.repository.pojo.github.GithubRepoBranch @@ -406,7 +407,9 @@ class GithubService @Autowired constructor( userId: String, projectId: String, refreshToken: Boolean?, - resetType: String? + resetType: String?, + redirectUrlType: RedirectUrlTypeEnum?, + redirectUrl: String? ): AuthorizeResult { logger.info("isOAuth userId is: $userId,refreshToken is: $refreshToken") val accessToken = if (refreshToken == true) { @@ -420,7 +423,9 @@ class GithubService @Autowired constructor( userId = userId, repoHashId = null, popupTag = "", - resetType = resetType + resetType = resetType, + specRedirectUrl = redirectUrl, + redirectUrlTypeEnum = redirectUrlType ).redirectUrl ) // 校验token是否有效 @@ -434,7 +439,9 @@ class GithubService @Autowired constructor( userId = userId, repoHashId = null, popupTag = "", - resetType = resetType + resetType = resetType, + specRedirectUrl = redirectUrl, + redirectUrlTypeEnum = redirectUrlType ).redirectUrl ) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt index 6a20fdff16bc..9dd4c33f643a 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/GithubTokenService.kt @@ -30,6 +30,7 @@ package com.tencent.devops.repository.service.github import com.tencent.devops.common.api.exception.CustomException import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.exception.RemoteServiceException +import com.tencent.devops.common.api.util.timestampmilli import com.tencent.devops.common.auth.api.AuthProjectApi import com.tencent.devops.common.auth.code.RepoAuthServiceCode import com.tencent.devops.common.client.Client @@ -57,18 +58,47 @@ class GithubTokenService @Autowired constructor( @Value("\${aes.github:#{null}}") private val aesKey = "" + /** + * 保存token + * @param userId server端的用户名 + * @param operator 蓝盾平台操作人用户名 + */ fun createAccessToken( userId: String, accessToken: String, tokenType: String, scope: String, - githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP + githubTokenType: GithubTokenType = GithubTokenType.GITHUB_APP, + operator: String ) { val encryptedAccessToken = BkCryptoUtil.encryptSm4ButAes(aesKey, accessToken) - if (githubTokenDao.getOrNull(dslContext, userId, githubTokenType) == null) { - githubTokenDao.create(dslContext, userId, encryptedAccessToken, tokenType, scope, githubTokenType) + val githubTokenRecord = githubTokenDao.getOrNull(dslContext, operator, githubTokenType) + if (githubTokenRecord == null) { + githubTokenDao.create( + dslContext = dslContext, + userId = userId, + accessToken = encryptedAccessToken, + tokenType = tokenType, + scope = scope, + githubTokenType = githubTokenType, + operator = operator + ) } else { - githubTokenDao.update(dslContext, userId, encryptedAccessToken, tokenType, scope, githubTokenType) + if (githubTokenRecord.operator != operator) { + logger.info( + "the operator of the gitHub token has changed|userId=$userId|" + + "operator=$operator|oldOperator=${githubTokenRecord.operator}" + ) + } + githubTokenDao.update( + dslContext = dslContext, + userId = userId, + accessToken = encryptedAccessToken, + tokenType = tokenType, + scope = scope, + githubTokenType = githubTokenType, + operator = operator + ) } } @@ -84,7 +114,10 @@ class GithubTokenService @Autowired constructor( return GithubToken( BkCryptoUtil.decryptSm4OrAes(aesKey, githubTokenRecord.accessToken), githubTokenRecord.tokenType, - githubTokenRecord.scope + githubTokenRecord.scope, + githubTokenRecord.createTime.timestampmilli(), + githubTokenRecord.userId, + githubTokenRecord.operator ) } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt index 9aeed354215f..d66da0736ea9 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/github/IGithubService.kt @@ -30,6 +30,7 @@ package com.tencent.devops.repository.service.github import com.tencent.devops.repository.pojo.AuthorizeResult import com.tencent.devops.repository.pojo.GithubCheckRuns import com.tencent.devops.repository.pojo.GithubCheckRunsResponse +import com.tencent.devops.repository.pojo.enums.RedirectUrlTypeEnum import com.tencent.devops.repository.pojo.github.GithubBranch import com.tencent.devops.repository.pojo.github.GithubTag import com.tencent.devops.repository.pojo.github.GithubToken @@ -69,7 +70,9 @@ interface IGithubService { userId: String, projectId: String, refreshToken: Boolean?, - resetType: String? + resetType: String?, + redirectUrlType: RedirectUrlTypeEnum? = null, + redirectUrl: String? = "" ): AuthorizeResult fun getAccessToken(userId: String): GithubToken? diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt new file mode 100644 index 000000000000..f26bd22c940d --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGitOauth2TokenStoreService.kt @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.service.oauth2 + +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.security.util.BkCryptoUtil +import com.tencent.devops.repository.dao.GitTokenDao +import com.tencent.devops.repository.pojo.CodeGitRepository +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken +import org.jooq.DSLContext +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +/** + * 内部工蜂oauth存储管理 + */ +@Service +class CodeGitOauth2TokenStoreService @Autowired constructor( + private val dslContext: DSLContext, + private val gitTokenDao: GitTokenDao +) : IOauth2TokenStoreService { + + @Value("\${aes.git:#{null}}") + private val aesKey: String = "" + + override fun support(scmCode: String): Boolean { + return scmCode == CodeGitRepository.SCM_CODE + } + + override fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return gitTokenDao.getAccessToken(dslContext, userId)?.let { + Oauth2AccessToken( + BkCryptoUtil.decryptSm4OrAes(aesKey, it.accessToken), + it.tokenType, + it.expiresIn, + BkCryptoUtil.decryptSm4OrAes(aesKey, it.refreshToken), + it.createTime.timestampmilli(), + userId = it.userId, + operator = it.operator + ) + } + } + + override fun delete(userId: String, scmCode: String) { + gitTokenDao.deleteToken(dslContext, userId) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.kt new file mode 100644 index 000000000000..0b3371a27805 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/CodeGithubOauth2TokenStoreService.kt @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.service.oauth2 + +import com.tencent.devops.common.api.util.timestampmilli +import com.tencent.devops.common.security.util.BkCryptoUtil +import com.tencent.devops.repository.dao.GithubTokenDao +import com.tencent.devops.repository.pojo.GithubRepository +import com.tencent.devops.repository.pojo.oauth.GithubTokenType +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken +import org.jooq.DSLContext +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +/** + * github oauth2 token存储管理 + */ +@Service +class CodeGithubOauth2TokenStoreService @Autowired constructor( + private val dslContext: DSLContext, + private val githubTokenDao: GithubTokenDao +) : IOauth2TokenStoreService { + + @Value("\${aes.github:#{null}}") + private val aesKey = "" + + override fun support(scmCode: String): Boolean { + return scmCode == GithubRepository.SCM_CODE + } + + override fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return githubTokenDao.getOrNull( + dslContext = dslContext, + userId = userId, + githubTokenType = GithubTokenType.GITHUB_APP + )?.let { + Oauth2AccessToken( + BkCryptoUtil.decryptSm4OrAes(aesKey, it.accessToken), + it.tokenType, + null, + null, + it.createTime.timestampmilli(), + it.userId, + it.operator + ) + } + } + + override fun delete(userId: String, scmCode: String) { + githubTokenDao.delete(dslContext, userId) + } +} diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.kt new file mode 100644 index 000000000000..5e10f3f3b244 --- /dev/null +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/IOauth2TokenStoreService.kt @@ -0,0 +1,41 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.repository.service.oauth2 + +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken + +/** + * oauth2 token存储服务 + */ +interface IOauth2TokenStoreService { + fun support(scmCode: String): Boolean + + fun get(userId: String, scmCode: String): Oauth2AccessToken? + + fun delete(userId: String, scmCode: String) +} diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/webhook/PipelineBuildPermissionService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt similarity index 62% rename from src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/webhook/PipelineBuildPermissionService.kt rename to src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt index 2bc61bb266db..aa47ec37c592 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/webhook/PipelineBuildPermissionService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/oauth2/Oauth2TokenStoreManager.kt @@ -23,32 +23,29 @@ * 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. - * */ -package com.tencent.devops.process.webhook +package com.tencent.devops.repository.service.oauth2 -import com.tencent.devops.common.auth.api.AuthPermission -import com.tencent.devops.common.web.utils.I18nUtil -import com.tencent.devops.process.constant.ProcessMessageCode -import com.tencent.devops.process.permission.PipelinePermissionService +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.repository.pojo.oauth.Oauth2AccessToken import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @Service -class PipelineBuildPermissionService @Autowired constructor( - private val pipelinePermissionService: PipelinePermissionService +class Oauth2TokenStoreManager @Autowired constructor( + private val oauth2TokenStoreServices: List ) { - fun checkPermission(userId: String, projectId: String, pipelineId: String) { - pipelinePermissionService.validPipelinePermission( - userId = userId, - projectId = projectId, - pipelineId = pipelineId, - permission = AuthPermission.EXECUTE, - message = I18nUtil.getCodeLanMessage( - messageCode = ProcessMessageCode.USER_NO_PIPELINE_PERMISSION_UNDER_PROJECT, - params = arrayOf(userId, projectId, AuthPermission.EXECUTE.getI18n(I18nUtil.getLanguage(userId))) - ) - ) + + fun get(userId: String, scmCode: String): Oauth2AccessToken? { + return getTokenStoreService(scmCode).get(userId, scmCode) + } + + fun delete(userId: String, scmCode: String) { + getTokenStoreService(scmCode).delete(userId, scmCode) + } + + private fun getTokenStoreService(scmCode: String): IOauth2TokenStoreService { + return oauth2TokenStoreServices.find { it.support(scmCode) } ?: throw ErrorCodeException(errorCode = "") } } diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt index 60801d6ce313..018d1b27e01a 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitOauthService.kt @@ -58,6 +58,7 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import java.net.URLDecoder import java.net.URLEncoder +import java.time.LocalDateTime @Service @Suppress("ALL") @@ -205,8 +206,11 @@ class GitOauthService @Autowired constructor( val userId = authParams["userId"] as String val gitProjectId = authParams["gitProjectId"] as String? val token = gitService.getToken(userId, code) + // 保存当前操作用户 + token.operator = userId // 在oauth授权过程中,可以输入公共账号去鉴权,所以需要再验证token所属人 val oauthUserId = gitService.getUserInfoByToken(token.accessToken).username ?: userId + logger.info("save the git access token for user $oauthUserId, operated by $userId") saveAccessToken(oauthUserId, token) val redirectUrl = gitService.getRedirectUrl(state) logger.info("gitCallback redirectUrl is: $redirectUrl") @@ -273,13 +277,16 @@ class GitOauthService @Autowired constructor( refreshToken = BkCryptoUtil.decryptSm4OrAes(aesKey, it.refreshToken), tokenType = it.tokenType, expiresIn = it.expiresIn, - createTime = it.createTime.timestampmilli() + createTime = it.createTime.timestampmilli(), + updateTime = LocalDateTime.now().timestampmilli(), + operator = it.operator ?: userId ) } } private fun refreshToken(userId: String, gitToken: GitToken): GitToken { val token = gitService.refreshToken(userId, gitToken) + token.operator = gitToken.operator saveAccessToken(userId, token) token.accessToken = BkCryptoUtil.decryptSm4OrAes(aesKey, token.accessToken) token.refreshToken = BkCryptoUtil.decryptSm4OrAes(aesKey, token.refreshToken) diff --git a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitService.kt b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitService.kt index 41b5374c101f..b52aaadf41e3 100644 --- a/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitService.kt +++ b/src/backend/ci/core/repository/biz-repository/src/main/kotlin/com/tencent/devops/repository/service/scm/GitService.kt @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.google.gson.JsonParser import com.tencent.devops.common.api.constant.CommonMessageCode +import com.tencent.devops.common.api.constant.ID import com.tencent.devops.common.api.enums.FrontendTypeEnum import com.tencent.devops.common.api.exception.CustomException import com.tencent.devops.common.api.pojo.Result @@ -40,7 +41,6 @@ import com.tencent.devops.common.api.util.HashUtil import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.OkhttpUtils import com.tencent.devops.common.api.util.OkhttpUtils.stringLimit -import com.tencent.devops.common.api.util.script.CommonScriptUtils import com.tencent.devops.common.service.prometheus.BkTimed import com.tencent.devops.common.service.utils.RetryUtils import com.tencent.devops.common.web.utils.I18nUtil @@ -53,8 +53,6 @@ import com.tencent.devops.repository.pojo.enums.TokenTypeEnum import com.tencent.devops.repository.pojo.enums.VisibilityLevelEnum import com.tencent.devops.repository.pojo.git.GitCodeFileInfo import com.tencent.devops.repository.pojo.git.GitCodeProjectInfo -import com.tencent.devops.scm.pojo.GitCreateBranch -import com.tencent.devops.scm.pojo.GitCreateMergeRequest import com.tencent.devops.repository.pojo.git.GitMrChangeInfo import com.tencent.devops.repository.pojo.git.GitOperationFile import com.tencent.devops.repository.pojo.git.GitUserInfo @@ -62,8 +60,6 @@ import com.tencent.devops.repository.pojo.git.UpdateGitProjectInfo import com.tencent.devops.repository.pojo.gitlab.GitlabFileInfo import com.tencent.devops.repository.pojo.oauth.GitToken import com.tencent.devops.repository.utils.scm.GitCodeUtils -import com.tencent.devops.scm.code.git.CodeGitOauthCredentialSetter -import com.tencent.devops.scm.code.git.CodeGitUsernameCredentialSetter import com.tencent.devops.scm.code.git.api.GitApi import com.tencent.devops.scm.code.git.api.GitBranch import com.tencent.devops.scm.code.git.api.GitBranchCommit @@ -80,6 +76,8 @@ import com.tencent.devops.scm.pojo.Commit import com.tencent.devops.scm.pojo.DownloadGitRepoFileRequest import com.tencent.devops.scm.pojo.GitCodeGroup import com.tencent.devops.scm.pojo.GitCommit +import com.tencent.devops.scm.pojo.GitCreateBranch +import com.tencent.devops.scm.pojo.GitCreateMergeRequest import com.tencent.devops.scm.pojo.GitDiff import com.tencent.devops.scm.pojo.GitFileInfo import com.tencent.devops.scm.pojo.GitListMergeRequest @@ -107,6 +105,11 @@ import javax.ws.rs.core.Response import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody +import org.eclipse.jgit.api.Git +import org.eclipse.jgit.storage.file.FileRepositoryBuilder +import org.eclipse.jgit.transport.CredentialsProvider +import org.eclipse.jgit.transport.RefSpec +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @@ -628,7 +631,8 @@ class GitService @Autowired constructor( ) } } - return Result(GitRepositoryResp(nameSpaceName, atomRepositoryUrl as String)) + val id = dataMap[ID] as Long + return Result(GitRepositoryResp(id, nameSpaceName, atomRepositoryUrl as String)) } } @@ -646,41 +650,41 @@ class GitService @Autowired constructor( logger.info("initRepositoryInfo atomTmpWorkspace is:${atomTmpWorkspace.absolutePath}") try { // 1、clone插件示例工程代码到插件工作空间下 - val credentialSetter = if (tokenType == TokenTypeEnum.OAUTH) { - CodeGitOauthCredentialSetter(token) + val credentialsProvider = if (tokenType == TokenTypeEnum.OAUTH) { + UsernamePasswordCredentialsProvider("oauth2", token) } else { - CodeGitUsernameCredentialSetter(gitPublicAccount, gitPublicSecret) + UsernamePasswordCredentialsProvider(gitPublicAccount, gitPublicSecret) } - CommonScriptUtils.execute( - script = "git clone ${credentialSetter.getCredentialUrl(sampleProjectPath)}", - dir = atomTmpWorkspace + cloneRepo( + workspace = atomTmpWorkspace, + remoteUrl = sampleProjectPath, + credentialsProvider = credentialsProvider ) // 2、删除下载下来示例工程的git信息 - val atomFileDir = atomTmpWorkspace.listFiles()?.firstOrNull() - logger.info("initRepositoryInfo atomFileDir is:${atomFileDir?.absolutePath}") - val atomGitFileDir = File(atomFileDir, ".git") + logger.info("initRepositoryInfo workspace is:${atomTmpWorkspace.absolutePath}") + val atomGitFileDir = File(atomTmpWorkspace, ".git") if (atomGitFileDir.exists()) { - FileSystemUtils.deleteRecursively(atomGitFileDir) + atomGitFileDir.deleteRecursively() } // 如果用户选的是自定义UI方式开发插件,则需要初始化UI开发脚手架 if (FrontendTypeEnum.SPECIAL == frontendType) { - val atomFrontendFileDir = File(atomFileDir, BK_FRONTEND_DIR_NAME) + val atomFrontendFileDir = File(atomGitFileDir, BK_FRONTEND_DIR_NAME) if (!atomFrontendFileDir.exists()) { atomFrontendFileDir.mkdirs() } - CommonScriptUtils.execute( - script = "git clone ${credentialSetter.getCredentialUrl(gitConfig.frontendSampleProjectUrl)}", - dir = atomFrontendFileDir + cloneRepo( + workspace = atomFrontendFileDir, + remoteUrl = gitConfig.frontendSampleProjectUrl, + credentialsProvider = credentialsProvider ) - val frontendProjectDir = atomFrontendFileDir.listFiles()?.firstOrNull() - logger.info("initRepositoryInfo frontendProjectDir is:${frontendProjectDir?.absolutePath}") - val frontendGitFileDir = File(frontendProjectDir, ".git") + logger.info("initRepositoryInfo atomFrontendFileDir is:${atomFrontendFileDir?.absolutePath}") + val frontendGitFileDir = File(atomFrontendFileDir, ".git") if (frontendGitFileDir.exists()) { FileSystemUtils.deleteRecursively(frontendGitFileDir) } } // 把task.json中的atomCode修改成用户对应的 - val taskJsonFile = File(atomFileDir, "task.json") + val taskJsonFile = File(atomTmpWorkspace, "task.json") if (taskJsonFile.exists()) { val taskJsonStr = taskJsonFile.readText(Charset.forName("UTF-8")) val taskJsonMap = JsonUtil.toMap(taskJsonStr).toMutableMap() @@ -692,20 +696,31 @@ class GitService @Autowired constructor( } } // 3、重新生成git信息 - CommonScriptUtils.execute("git init", atomFileDir) + Git.init().setDirectory(atomTmpWorkspace).setInitialBranch("master").call().close() + val gitRepository = FileRepositoryBuilder() + .setGitDir(atomGitFileDir) + .readEnvironment() + .findGitDir() + .build() // 4、添加远程仓库 - CommonScriptUtils.execute( - script = "git remote add origin ${credentialSetter.getCredentialUrl(atomRepositoryUrl)}", - dir = atomFileDir - ) + gitRepository.config.setString("remote", "origin", "url", atomRepositoryUrl) // 5、给文件添加git信息 - CommonScriptUtils.execute("git config user.email \"$gitPublicEmail\"", atomFileDir) - CommonScriptUtils.execute("git config user.name \"$gitPublicAccount\"", atomFileDir) - CommonScriptUtils.execute("git add .", atomFileDir) + gitRepository.config.setString("user", null, "name", gitPublicAccount) + gitRepository.config.setString("user", null, "email", gitPublicEmail) + gitRepository.config.save() + val gitOperation = Git(gitRepository) + gitOperation.add().addFilepattern(".").call() // 6、提交本地文件 - CommonScriptUtils.execute("git commit -m init", atomFileDir) + gitOperation.commit().setMessage("init").call() // 7、提交代码到远程仓库 - CommonScriptUtils.execute("git push origin master", atomFileDir) + gitOperation + .push() + .setCredentialsProvider(credentialsProvider) + .setRemote("origin") + .setForce(true) + .setRefSpecs(RefSpec("refs/heads/master:refs/heads/master")) + .call() + gitOperation.close() logger.info("initRepositoryInfo finish") } catch (e: Exception) { logger.error("initRepositoryInfo error is:", e) @@ -2081,4 +2096,15 @@ class GitService @Autowired constructor( } return Result(mrInfo) } + + private fun cloneRepo(workspace: File, remoteUrl: String, credentialsProvider: CredentialsProvider) { + logger.info("init repo|start clone [$remoteUrl]") + // 克隆远程仓库 + val cloneRepo = Git.cloneRepository() + .setURI(remoteUrl) + .setDirectory(workspace) + .setCredentialsProvider(credentialsProvider) + .call() + cloneRepo.close() + } } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/BuildStoreResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/BuildStoreResource.kt index 7cbb54d9a152..4a27a9acecbe 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/BuildStoreResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/BuildStoreResource.kt @@ -28,6 +28,7 @@ package com.tencent.devops.store.api.common import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_BUILD_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_ENV import com.tencent.devops.common.api.auth.AUTH_HEADER_DEVOPS_VM_SEQ_ID import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.store.pojo.common.sensitive.SensitiveConfResp @@ -73,6 +74,9 @@ interface BuildStoreResource { @GET @Path("/pkg/envs/types/{storeType}/languages/{language}/versions/{runtimeVersion}/get") fun getStorePkgRunEnvInfo( + @Parameter(description = "环境信息", required = false) + @HeaderParam(AUTH_HEADER_DEVOPS_ENV) + devopsEnv: String? = null, @Parameter(description = "组件类型", required = true) @PathParam("storeType") storeType: StoreTypeEnum, diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/OpStoreResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/OpStoreResource.kt new file mode 100644 index 000000000000..3dda6c57b12d --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/OpStoreResource.kt @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.api.common + +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.HeaderParam +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "OP_STORE", description = "OP-商店") +@Path("/op/store") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface OpStoreResource { + + @Operation(summary = "删除组件内置流水线") + @DELETE + @Path("/inner/pipeline/delete") + fun deleteStoreInnerPipeline( + @Parameter(description = "用户ID", required = true, example = AUTH_HEADER_USER_ID_DEFAULT_VALUE) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = false) + @QueryParam("storeType") + storeType: StoreTypeEnum? = null, + @Parameter(description = "组件标识", required = false) + @QueryParam("storeCode") + storeCode: String? = null, + @Parameter(description = "需排除的项目", required = false) + @QueryParam("excludeProjectCode") + excludeProjectCode: String? = null + ): Result +} diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreComponentResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreComponentResource.kt index 80613600e52f..39c033582ee8 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreComponentResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreComponentResource.kt @@ -157,6 +157,9 @@ interface ServiceStoreComponentResource { @Parameter(description = "实例ID", required = false) @QueryParam("instanceId") instanceId: String?, + @Parameter(description = "是否查测试中版本 true:是,false:否", required = false) + @QueryParam("queryTestFlag") + queryTestFlag: Boolean? = null, @Parameter(description = "页码", required = true) @QueryParam("page") @BkField(patternStyle = BkStyleEnum.NUMBER_STYLE) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreResource.kt index 5e7a2ab0350c..84f1c10832ed 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/ServiceStoreResource.kt @@ -109,6 +109,30 @@ interface ServiceStoreResource { userId: String ): Result + @Operation(summary = "判断项目是否是研发商店公共项目") + @GET + @Path("projects/{projectCode}/validate") + fun isPublicProject( + @Parameter(description = "标识", required = true) + @PathParam("projectCode") + projectCode: String + ): Result + + @Operation(summary = "校验流水线用户访问组件信息权限") + @GET + @Path("/codes/{storeCode}/pipeline/visit/validate") + fun validatePipelineUserStorePermission( + @Parameter(description = "标识", required = true) + @PathParam("storeCode") + storeCode: String, + @Parameter(description = "类型", required = true) + @QueryParam("storeType") + storeType: StoreTypeEnum, + @Parameter(description = "用户ID", required = true) + @QueryParam("userId") + userId: String + ): Result + @Operation(summary = "判断错误码是否合规") @POST @Path("/codes/{storeCode}/errorCode/compliance") diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentManageResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentManageResource.kt index 09a8831d6c22..acdd6748c8f9 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentManageResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentManageResource.kt @@ -34,6 +34,8 @@ import com.tencent.devops.common.web.constant.BkStyleEnum import com.tencent.devops.store.pojo.common.InstallStoreReq import com.tencent.devops.store.pojo.common.StoreBaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.UnInstallReq +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.test.StoreTestRequest import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.tags.Tag @@ -121,4 +123,40 @@ interface UserStoreComponentManageResource { @BkField(patternStyle = BkStyleEnum.CODE_STYLE) storeCode: String ): Result + + @Operation(summary = "更改组件授权人信息") + @PUT + @Path("/types/{storeType}/codes/{storeCode}/component/repository/authorizer/update") + fun updateStoreRepositoryAuthorizer( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: StoreTypeEnum, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String + ): Result + + @Operation(summary = "保存组件测试信息") + @PUT + @Path("/types/{storeType}/codes/{storeCode}/component/test/info/save") + fun saveStoreTestInfo( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: StoreTypeEnum, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String, + @Parameter(description = "组件测试信息请求报文", required = true) + storeTestRequest: StoreTestRequest + ): Result } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentQueryResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentQueryResource.kt index c6cb9267aed9..721144a93411 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentQueryResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreComponentQueryResource.kt @@ -38,6 +38,9 @@ import com.tencent.devops.store.pojo.common.MyStoreComponent import com.tencent.devops.store.pojo.common.StoreDetailInfo import com.tencent.devops.store.pojo.common.enums.RdTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreSortTypeEnum +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.media.StoreMediaInfo +import com.tencent.devops.store.pojo.common.test.StoreTestItem import com.tencent.devops.store.pojo.common.version.StoreDeskVersionItem import com.tencent.devops.store.pojo.common.version.StoreShowVersionInfo import io.swagger.v3.oas.annotations.Operation @@ -253,6 +256,9 @@ interface UserStoreComponentQueryResource { @Parameter(description = "实例ID", required = false) @QueryParam("instanceId") instanceId: String?, + @Parameter(description = "是否查测试中版本 true:是,false:否", required = false) + @QueryParam("queryTestFlag") + queryTestFlag: Boolean?, @Parameter(description = "页码", required = true) @QueryParam("page") @BkField(patternStyle = BkStyleEnum.NUMBER_STYLE) @@ -279,4 +285,38 @@ interface UserStoreComponentQueryResource { @BkField(patternStyle = BkStyleEnum.CODE_STYLE) storeCode: String ): Result + + @Operation(summary = "获取组件测试信息") + @GET + @Path("/types/{storeType}/codes/{storeCode}/component/test/info/get") + fun getStoreTestInfo( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: StoreTypeEnum, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String + ): Result> + + @Operation(summary = "获取组件媒体信息") + @Path("/types/{storeType}/codes/{storeCode}/component/media/info/get") + @GET + fun getStoreMediaInfo( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: StoreTypeEnum, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String + ): Result?> } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreReleaseResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreReleaseResource.kt index 4e7d86eaec43..e7acf472b9a8 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreReleaseResource.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreReleaseResource.kt @@ -29,6 +29,7 @@ package com.tencent.devops.store.api.common import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.store.pojo.common.StoreReleaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.publication.StoreProcessInfo import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateResponse @@ -115,6 +116,21 @@ interface UserStoreReleaseResource { storeId: String ): Result + @Operation(summary = "填写信息") + @PUT + @Path("/components/{storeId}/release/info/edit") + fun editReleaseInfo( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "storeId", required = true) + @PathParam("storeId") + storeId: String, + @Parameter(description = "填写信息请求报文体", required = true) + @Valid + storeReleaseInfoUpdateRequest: StoreReleaseInfoUpdateRequest + ): Result + @Operation(summary = "下架组件") @PUT @Path("/component/offline") @@ -138,4 +154,16 @@ interface UserStoreReleaseResource { @PathParam("storeId") storeId: String ): Result + + @Operation(summary = "返回上一步") + @PUT + @Path("/components/{storeId}/step/back") + fun back( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件Id", required = true) + @PathParam("storeId") + storeId: String + ): Result } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreVisibleDeptResource.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreVisibleDeptResource.kt new file mode 100644 index 000000000000..9c1fe599e0d1 --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/api/common/UserStoreVisibleDeptResource.kt @@ -0,0 +1,109 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.api.common + +import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.annotation.BkField +import com.tencent.devops.common.web.constant.BkStyleEnum +import com.tencent.devops.store.pojo.common.enums.DeptStatusEnum +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptReq +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptResp +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.tags.Tag +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.DefaultValue +import javax.ws.rs.GET +import javax.ws.rs.HeaderParam +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.PathParam +import javax.ws.rs.Produces +import javax.ws.rs.QueryParam +import javax.ws.rs.core.MediaType + +@Tag(name = "USER_STORE_VISIBLE", description = "研发商店-可见范围") +@Path("/user/store/visibilities") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +interface UserStoreVisibleDeptResource { + + @Operation(summary = "设置组件可见范围") + @POST + @Path("/add") + fun addVisibleDept( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件可见范围请求报文体", required = true) + storeVisibleDeptReq: StoreVisibleDeptReq + ): Result + + @Operation(summary = "查看组件可见范围") + @GET + @Path("/types/{storeType}/codes/{storeCode}/get") + fun getVisibleDept( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: String, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String, + @Parameter(description = "机构状态集合,用\",\"分隔进行拼接(如APPROVING,APPROVED,REJECT)", required = false) + @QueryParam("deptStatusInfos") + @DefaultValue("APPROVED") + deptStatusInfos: String? = DeptStatusEnum.APPROVED.name + ): Result + + @Operation(summary = "删除组件可见范围") + @DELETE + @Path("/types/{storeType}/codes/{storeCode}/delete") + fun deleteVisibleDept( + @Parameter(description = "userId", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "组件类型", required = true) + @PathParam("storeType") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeType: String, + @Parameter(description = "组件代码", required = true) + @PathParam("storeCode") + @BkField(patternStyle = BkStyleEnum.CODE_STYLE) + storeCode: String, + @Parameter(description = "机构Id集合,用\",\"分隔进行拼接(如1,2,3)", required = true) + @QueryParam("deptIds") + deptIds: String + ): Result +} diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreConstants.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreConstants.kt index 2f6edd337e86..6fce462dc77c 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreConstants.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreConstants.kt @@ -73,4 +73,5 @@ object StoreConstants { const val BK_DEFAULT_FAIL_POLICY = "defaultFailPolicy" const val BK_DEFAULT_RETRY_POLICY = "defaultRetryPolicy" const val BK_RETRY_TIMES = "retryTimes" + const val KEY_FRAMEWORK_CODE = "frameworkCode" } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt index 13620f86d27c..e451068e0fa0 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/constant/StoreMessageCode.kt @@ -169,4 +169,5 @@ object StoreMessageCode { // 当 queryProjectComponentFlag、installed 或 updateFlag 参数不为空时, projectCode 参数必须非空 const val STORE_QUERY_PARAM_CHECK_FAIL = "2120935" const val STORE_COMPONENT_IS_NOT_ALLOW_OFFLINE = "2120936" // 研发商店:非发布状态的版本不允许下架 + const val STORE_COMPONENT_CODE_REPOSITORY_DELETE_FAIL = "2120937" // 研发商店:代码库删除失败,失败原因:{0} } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomVisibleDeptReq.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomVisibleDeptReq.kt index 14e4fbd5b726..2d84d93f132a 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomVisibleDeptReq.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/AtomVisibleDeptReq.kt @@ -27,7 +27,7 @@ package com.tencent.devops.store.pojo.atom -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "插件市场-插件可见范围请求报文体") diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomUpdateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomUpdateRequest.kt index 8c47cc6ad857..145477b496cd 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomUpdateRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/atom/MarketAtomUpdateRequest.kt @@ -73,7 +73,7 @@ data class MarketAtomUpdateRequest( @get:Schema(title = "插件字段校验确认标识", required = false) val fieldCheckConfirmFlag: Boolean? = false, @get:Schema(title = "分支", required = false) - val branch: String? = null, + var branch: String? = null, @get:Schema(title = "是否属于分支测试版本", required = false) var isBranchTestVersion: Boolean = false ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/MarketItem.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/MarketItem.kt index f17afc4124bb..2fbd075489f9 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/MarketItem.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/MarketItem.kt @@ -45,6 +45,8 @@ data class MarketItem( val code: String, @get:Schema(title = "版本号", required = true) val version: String, + @get:Schema(title = "状态", required = true) + val status: String, @get:Schema(title = "组件类型", required = true) val type: String, @get:Schema(title = "研发来源类型", required = false) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/QueryComponentsParam.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/QueryComponentsParam.kt index 6c3ba09a77ff..6c28acd0a8f6 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/QueryComponentsParam.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/QueryComponentsParam.kt @@ -35,21 +35,21 @@ data class QueryComponentsParam( @get:Schema(title = "组件类型", required = true) val storeType: String, @get:Schema(title = "类型", required = false) - val type: String?, + val type: String? = null, @get:Schema(title = "store组件名称", required = false) - val name: String?, + val name: String? = null, @get:Schema(title = "是否处于流程中", required = false) val processFlag: Boolean? = null, @get:Schema(title = "分类", required = false) val classifyCode: String? = null, @get:Schema(title = "应用范畴,多个用逗号分隔", required = false) - val categoryCodes: String?, + val categoryCodes: String? = null, @get:Schema(title = "功能标签,多个用逗号分隔", required = false) - val labelCodes: String?, + val labelCodes: String? = null, @get:Schema(title = "排序", required = false) - val sortType: StoreSortTypeEnum?, + val sortType: StoreSortTypeEnum? = null, @get:Schema(title = "页码", required = true) - val page: Int, + val page: Int = 1, @get:Schema(title = "每页数量", required = true) - val pageSize: Int + val pageSize: Int = 10 ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreConstants.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreConstants.kt index ffe9f6fee893..91ad103bae3d 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreConstants.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreConstants.kt @@ -36,7 +36,7 @@ const val TASK_JSON_NAME = "task.json" const val QUALITY_JSON_NAME = "quality.json" const val ERROR_JSON_NAME = "errorCodes.json" const val EXTENSION_JSON_NAME = "extension.json" -const val CONFIG_JSON_NAME = "bk-config.json" +const val CONFIG_YML_NAME = "bk-config.yml" const val README = "README.md" const val STORE_ATOM_STATUS = "STORE_ATOM_STATUS" // 插件状态 const val STORE_IMAGE_STATUS = "STORE_IMAGE_STATUS" // 镜像状态 @@ -146,6 +146,7 @@ const val KEY_RELEASE_INFO = "releaseInfo" const val KEY_VERSION_INFO = "versionInfo" const val KEY_RELEASE_TYPE = "releaseType" const val KEY_YAML_FLAG = "yamlFlag" +const val KEY_REPOSITORY_AUTHORIZER = "repositoryAuthorizer" const val STORE_INDEX_CODE = "INDEX_CODE" const val STORE_INDEX_NAME = "INDEX_NAME" diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreDetailInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreDetailInfo.kt index 0ef29754f542..00fdfb309204 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreDetailInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreDetailInfo.kt @@ -87,8 +87,10 @@ data class StoreDetailInfo( val recommendFlag: Boolean, @get:Schema(title = "是否官方认证 true:是 false:否", required = true) val certificationFlag: Boolean, - @get:Schema(title = "类型", required = false) + @get:Schema(title = "应用类型", required = false) val type: String?, + @get:Schema(title = "研发类型", required = false) + val rdType: String?, @get:Schema(title = "用户评论信息", required = true) val userCommentInfo: StoreUserCommentInfo, @get:Schema(title = "是否可编辑", required = false) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreInfoQuery.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreInfoQuery.kt index fa058e36f43c..30b72878624a 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreInfoQuery.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreInfoQuery.kt @@ -67,6 +67,8 @@ data class StoreInfoQuery( val sortType: StoreSortTypeEnum? = null, @get:Schema(title = "实例ID", required = false) val instanceId: String? = null, + @get:Schema(title = "是否查测试中版本 true:是,false:否", required = false) + val queryTestFlag: Boolean? = null, @get:Schema(title = "页码", required = true) val page: Int, @get:Schema(title = "每页数量", required = true) @@ -74,9 +76,12 @@ data class StoreInfoQuery( ) { fun validate() { // 检查 projectCode 是否为空 - val paramCheck = (queryProjectComponentFlag || installed != null || updateFlag != null) - if (paramCheck && projectCode.isNullOrEmpty()) { + if (getSpecQueryFlag() && projectCode.isNullOrEmpty()) { throw ErrorCodeException(errorCode = StoreMessageCode.STORE_QUERY_PARAM_CHECK_FAIL) } } + + fun getSpecQueryFlag(): Boolean { + return queryProjectComponentFlag || installed != null || updateFlag != null || queryTestFlag != null + } } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreReleaseInfoUpdateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreReleaseInfoUpdateRequest.kt new file mode 100644 index 000000000000..20d2d06dbe3d --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/StoreReleaseInfoUpdateRequest.kt @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common + +import com.tencent.devops.store.pojo.common.media.MediaInfoReq +import com.tencent.devops.store.pojo.common.visible.DeptInfo +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "研发商店-组件发布信息修改请求报文体") +data class StoreReleaseInfoUpdateRequest( + @get:Schema(title = "媒体信息", required = false) + val mediaInfoList: List? = null, + @get:Schema(title = "机构列表", required = true) + val deptInfoList: List? = null +) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/FrameworkCodeEnum.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/FrameworkCodeEnum.kt new file mode 100644 index 000000000000..d80da5752990 --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/FrameworkCodeEnum.kt @@ -0,0 +1,33 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common.enums + +enum class FrameworkCodeEnum { + NODEJS_FRAMEWORK, // nodejs开发框架 + CUSTOM_FRAMEWORK; // 自定义开发框架 +} diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/StoreStatusEnum.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/StoreStatusEnum.kt index 4ec0a4d3c8a8..be5784982b28 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/StoreStatusEnum.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/enums/StoreStatusEnum.kt @@ -37,6 +37,7 @@ enum class StoreStatusEnum { CHECKING, // 验证中 CHECK_FAIL, // 验证失败 TESTING, // 测试中 + EDITING, // 填写信息中 AUDITING, // 审核中 AUDIT_REJECT, // 审核驳回 RELEASED, // 已发布 @@ -66,5 +67,13 @@ enum class StoreStatusEnum { AUDITING.name ) } + + fun getTestStatusList(): List { + return listOf( + TESTING.name, + EDITING.name, + AUDITING.name + ) + } } } diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/env/StorePkgRunEnvInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/env/StorePkgRunEnvInfo.kt index 42868a44fb31..5cf648db098c 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/env/StorePkgRunEnvInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/env/StorePkgRunEnvInfo.kt @@ -46,7 +46,7 @@ data class StorePkgRunEnvInfo( @get:Schema(title = "安装包名称", required = true) val pkgName: String, @get:Schema(title = "安装包下载路径", required = true) - val pkgDownloadPath: String, + var pkgDownloadPath: String, @get:Schema(title = "是否为默认安装包", required = true) val defaultFlag: Boolean, @get:Schema(title = "添加用户", required = true) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/media/StoreMediaInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/media/StoreMediaInfo.kt index 1749ea1b7e82..edc1f024e0e6 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/media/StoreMediaInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/media/StoreMediaInfo.kt @@ -32,7 +32,7 @@ import io.swagger.v3.oas.annotations.media.Schema data class StoreMediaInfo( @get:Schema(title = "媒体id") val id: String, - @get:Schema(title = "研发商店类型", required = true) + @get:Schema(title = "组件标识", required = true) val storeCode: String, @get:Schema(title = "媒体url", required = true) val mediaUrl: String, diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseEnvRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseEnvRequest.kt index 4cab91ec9f26..37a39771ceb1 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseEnvRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseEnvRequest.kt @@ -34,5 +34,5 @@ data class StoreBaseEnvRequest( @get:Schema(title = "语言", required = false) val language: String? = null, @get:Schema(title = "环境扩展信息", required = false) - val extBaseEnvInfo: Map? = null + val extBaseEnvInfo: MutableMap? = null ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseFeatureRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseFeatureRequest.kt index b8c17855661d..cfa3c77dfad8 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseFeatureRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseFeatureRequest.kt @@ -37,5 +37,5 @@ data class StoreBaseFeatureRequest( @get:Schema(title = "研发类型", required = false) val rdType: RdTypeEnum? = null, @get:Schema(title = "特性扩展信息", required = false) - val extBaseFeatureInfo: Map? = null + val extBaseFeatureInfo: MutableMap? = null ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseUpdateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseUpdateRequest.kt index c6f3f83a789e..00d4bd9b0fe7 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseUpdateRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreBaseUpdateRequest.kt @@ -67,5 +67,5 @@ data class StoreBaseUpdateRequest( val baseFeatureInfo: StoreBaseFeatureRequest? = null, @get:Schema(title = "环境信息列表", required = false) @Valid - val baseEnvInfos: List? = null + val baseEnvInfos: MutableList? = null ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreCreateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreCreateRequest.kt index 611654b17d91..4224cf0dd3a1 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreCreateRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreCreateRequest.kt @@ -33,8 +33,8 @@ import javax.validation.Valid @Schema(title = "工作台-新增组件请求报文体") data class StoreCreateRequest( - @get:Schema(title = "项目代码", required = true) - val projectCode: String, + @get:Schema(title = "项目代码", required = false) + val projectCode: String? = null, @get:Schema(title = "基础信息", required = true) @field:Valid val baseInfo: StoreBaseCreateRequest diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreUpdateRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreUpdateRequest.kt index 29b5dc6ee3a6..269b9d4f3dd8 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreUpdateRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/publication/StoreUpdateRequest.kt @@ -33,8 +33,8 @@ import javax.validation.Valid @Schema(title = "工作台-更新组件请求报文体") data class StoreUpdateRequest( - @get:Schema(title = "项目代码", required = true) - val projectCode: String, + @get:Schema(title = "项目代码", required = false) + val projectCode: String? = null, @get:Schema(title = "基础信息", required = true) @field:Valid val baseInfo: StoreBaseUpdateRequest diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestItem.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestItem.kt new file mode 100644 index 000000000000..2a50ad9d163c --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestItem.kt @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common.test + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "测试项信息") +data class StoreTestItem( + @get:Schema(title = "项目代码", required = true) + val projectCode: String, + @get:Schema(title = "实例ID", required = false) + val instanceId: String? = null +) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestRequest.kt new file mode 100644 index 000000000000..6bf63528db02 --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/test/StoreTestRequest.kt @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common.test + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "组件测试信息请求报文") +data class StoreTestRequest( + @get:Schema(title = "测试信息列表") + val testItems: List +) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/DeptInfo.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/DeptInfo.kt similarity index 89% rename from src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/DeptInfo.kt rename to src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/DeptInfo.kt index 9ef349e74017..ec7ddab5fe87 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/DeptInfo.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/DeptInfo.kt @@ -25,7 +25,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.tencent.devops.store.pojo.common +package com.tencent.devops.store.pojo.common.visible import io.swagger.v3.oas.annotations.media.Schema @@ -35,8 +35,8 @@ data class DeptInfo( val deptId: Int, @get:Schema(title = "机构名称", required = true) val deptName: String, - @get:Schema(title = "机构审核状态(0:待审核 1:审核通过 2:审核驳回)", required = false) - val status: String? = null, + @get:Schema(title = "机构审核状态(APPROVING:待审核 APPROVED:审核通过 REJECT:审核驳回)", required = false) + var status: String? = null, @get:Schema(title = "批注", required = false) val comment: String? = null ) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptReq.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptReq.kt new file mode 100644 index 000000000000..310894e1dcfd --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptReq.kt @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common.visible + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "组件可见范围请求报文体") +data class StoreVisibleDeptReq( + @get:Schema(title = "组件类型", required = true) + val storeType: String, + @get:Schema(title = "组件代码", required = true) + val storeCode: String, + @get:Schema(title = "机构列表", required = true) + val deptInfos: List +) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptResp.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptResp.kt new file mode 100644 index 000000000000..952837efdb90 --- /dev/null +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/StoreVisibleDeptResp.kt @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.pojo.common.visible + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "store组件可见范围响应报文体") +data class StoreVisibleDeptResp( + @get:Schema(title = "机构列表", required = true) + val deptInfos: List +) diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/UserStoreDeptInfoRequest.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/UserStoreDeptInfoRequest.kt similarity index 97% rename from src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/UserStoreDeptInfoRequest.kt rename to src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/UserStoreDeptInfoRequest.kt index 136d8470bd27..f2c122ee9fe6 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/UserStoreDeptInfoRequest.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/common/visible/UserStoreDeptInfoRequest.kt @@ -25,7 +25,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.tencent.devops.store.pojo.common +package com.tencent.devops.store.pojo.common.visible import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import io.swagger.v3.oas.annotations.media.Schema diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/request/ImageVisibleDeptReq.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/request/ImageVisibleDeptReq.kt index 6083ece281ca..32b9c5c270b3 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/request/ImageVisibleDeptReq.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/request/ImageVisibleDeptReq.kt @@ -26,7 +26,7 @@ */ package com.tencent.devops.store.pojo.image.request -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "镜像市场-镜像可见范围请求报文体") diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/response/MarketImageItem.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/response/MarketImageItem.kt index 0b5320192afc..a74a3677a63b 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/response/MarketImageItem.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/image/response/MarketImageItem.kt @@ -68,6 +68,9 @@ data class MarketImageItem constructor( @get:Schema(title = "版本号", required = true) val version: String, + @get:Schema(title = "状态", required = true) + val status: String, + @get:Schema(title = "镜像简介", required = false) val summary: String? = null, @@ -127,6 +130,7 @@ data class MarketImageItem constructor( instance.classifyCode, instance.logoUrl, instance.version, + instance.status, instance.summary, instance.score, instance.downloads, diff --git a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/template/TemplateVisibleDeptReq.kt b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/template/TemplateVisibleDeptReq.kt index 4c373908069f..c265ffeee57c 100644 --- a/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/template/TemplateVisibleDeptReq.kt +++ b/src/backend/ci/core/store/api-store/src/main/kotlin/com/tencent/devops/store/pojo/template/TemplateVisibleDeptReq.kt @@ -27,7 +27,7 @@ package com.tencent.devops.store.pojo.template -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo import io.swagger.v3.oas.annotations.media.Schema @Schema(title = "模板可见范围请求报文体") diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomCommonDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomCommonDao.kt index 7682cd6f102a..83203a309a6b 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomCommonDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomCommonDao.kt @@ -44,6 +44,7 @@ import com.tencent.devops.store.pojo.common.KEY_CREATOR import com.tencent.devops.store.pojo.common.KEY_LANGUAGE import com.tencent.devops.store.pojo.common.KEY_PROJECT_CODE import com.tencent.devops.store.pojo.common.KEY_STORE_CODE +import com.tencent.devops.store.pojo.common.STORE_CODE import com.tencent.devops.store.pojo.common.StoreBaseInfo import com.tencent.devops.store.pojo.common.enums.StoreProjectTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum @@ -193,4 +194,10 @@ class AtomCommonDao : AbstractStoreCommonDao() { .fetchAny()?.into(String::class.java) } } + + override fun getStoreCodeById(dslContext: DSLContext, storeId: String): String? { + return with(TAtom.T_ATOM) { + dslContext.select(ATOM_CODE).from(this).where(ID.eq(storeId)).fetchOne(0, String::class.java) + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt index f127eb0e29ee..974677e06bbb 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/AtomDao.kt @@ -215,18 +215,19 @@ class AtomDao : AtomBaseDao() { * 统计还在使用处于下架中或者已下架状态的插件的项目的个数 */ fun countUndercarriageAtomNumByClassifyId(dslContext: DSLContext, classifyId: String): Int { - val a = TAtom.T_ATOM.`as`("a") - val b = TStoreProjectRel.T_STORE_PROJECT_REL.`as`("b") + val tAtom = TAtom.T_ATOM + val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL val atomStatusList = listOf( AtomStatusEnum.UNDERCARRIAGING.status.toByte(), AtomStatusEnum.UNDERCARRIAGED.status.toByte() ) - return dslContext.selectCount().from(a).join(b).on(a.ATOM_CODE.eq(b.STORE_CODE)) + return dslContext.select(countDistinct(tStoreProjectRel.PROJECT_CODE)).from(tAtom).join(tStoreProjectRel) + .on(tAtom.ATOM_CODE.eq(tStoreProjectRel.STORE_CODE)) .where( - a.ATOM_STATUS.`in`(atomStatusList) - .and(a.CLASSIFY_ID.eq(classifyId)) - ) - .fetchOne(0, Int::class.java)!! + tAtom.ATOM_STATUS.`in`(atomStatusList) + .and(tAtom.CLASSIFY_ID.eq(classifyId)) + .and(tStoreProjectRel.STORE_TYPE.eq(StoreTypeEnum.ATOM.type.toByte())) + ).fetchOne(0, Int::class.java)!! } fun delete(dslContext: DSLContext, id: String) { @@ -1198,7 +1199,7 @@ class AtomDao : AtomBaseDao() { listOf( AtomStatusEnum.UNDERCARRIAGING.status.toByte(), AtomStatusEnum.UNDERCARRIAGED.status.toByte(), - AtomStatusEnum.RELEASED.status.toByte(), + AtomStatusEnum.RELEASED.status.toByte() ) )) } else { diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomDao.kt index ac167b19850b..4ebc8e16302e 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomDao.kt @@ -159,6 +159,7 @@ class MarketAtomDao : AtomBaseDao() { ta.CATEGROY, ta.ATOM_CODE, ta.VERSION, + ta.ATOM_STATUS, ta.LOGO_URL, ta.PUBLISHER, ta.SUMMARY, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomEnvInfoDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomEnvInfoDao.kt index e5b66c191f1a..c12bb55b84f1 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomEnvInfoDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/dao/MarketAtomEnvInfoDao.kt @@ -102,7 +102,8 @@ class MarketAtomEnvInfoDao { atomCode: String, version: String, atomDefaultFlag: Boolean, - atomStatusList: List? + atomStatusList: List?, + queryProjectFlag: Boolean = true ): Record? { val tAtom = TAtom.T_ATOM val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL @@ -115,17 +116,21 @@ class MarketAtomEnvInfoDao { atomStatusList = atomStatusList )).orderBy(tAtom.CREATE_TIME.desc()).limit(1).fetchOne() } else { - getAtomBaseInfoStep(dslContext, tAtom) - .join(tStoreProjectRel) - .on(tAtom.ATOM_CODE.eq(tStoreProjectRel.STORE_CODE)) - .where(queryNormalAtomCondition( + val baseStep = getAtomBaseInfoStep(dslContext, tAtom) + if (queryProjectFlag) { + baseStep.join(tStoreProjectRel).on(tAtom.ATOM_CODE.eq(tStoreProjectRel.STORE_CODE)) + } + baseStep.where( + queryNormalAtomCondition( tAtom = tAtom, tStoreProjectRel = tStoreProjectRel, projectCode = projectCode, atomCode = atomCode, version = version, - atomStatusList = atomStatusList - )).orderBy(tAtom.CREATE_TIME.desc()).limit(1).fetchOne() + atomStatusList = atomStatusList, + queryProjectFlag = queryProjectFlag + ) + ).orderBy(tAtom.CREATE_TIME.desc()).limit(1).fetchOne() } } @@ -164,7 +169,7 @@ class MarketAtomEnvInfoDao { val conditions = mutableListOf() conditions.add(tAtom.ATOM_CODE.eq(atomCode)) conditions.add(tAtom.VERSION.like(VersionUtils.generateQueryVersion(version))) - if (atomStatusList != null && atomStatusList.isNotEmpty()) { + if (!atomStatusList.isNullOrEmpty()) { conditions.add(tAtom.ATOM_STATUS.`in`(atomStatusList)) } return conditions @@ -187,12 +192,15 @@ class MarketAtomEnvInfoDao { projectCode: String, atomCode: String, version: String, - atomStatusList: List? + atomStatusList: List?, + queryProjectFlag: Boolean = true ): MutableList { val conditions = getBaseQueryCondition(tAtom, atomCode, version, atomStatusList) conditions.add(tAtom.DEFAULT_FLAG.eq(false)) // 查普通插件 - conditions.add(tStoreProjectRel.PROJECT_CODE.eq(projectCode)) - conditions.add(tStoreProjectRel.STORE_TYPE.eq(StoreTypeEnum.ATOM.type.toByte())) + if (queryProjectFlag) { + conditions.add(tStoreProjectRel.PROJECT_CODE.eq(projectCode)) + conditions.add(tStoreProjectRel.STORE_TYPE.eq(StoreTypeEnum.ATOM.type.toByte())) + } return conditions } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomHandleBuildResultServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomHandleBuildResultServiceImpl.kt index 43d8013f9371..4e6f387247c5 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomHandleBuildResultServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomHandleBuildResultServiceImpl.kt @@ -34,14 +34,14 @@ import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.store.atom.dao.MarketAtomDao -import com.tencent.devops.store.pojo.atom.enums.AtomStatusEnum -import com.tencent.devops.store.pojo.common.ATOM_POST_VERSION_TEST_FLAG_KEY_PREFIX -import com.tencent.devops.store.pojo.common.STORE_LATEST_TEST_FLAG_KEY_PREFIX -import com.tencent.devops.store.pojo.common.publication.StoreBuildResultRequest import com.tencent.devops.store.atom.service.AtomReleaseService import com.tencent.devops.store.atom.service.MarketAtomService import com.tencent.devops.store.common.service.AbstractStoreHandleBuildResultService import com.tencent.devops.store.common.utils.VersionUtils +import com.tencent.devops.store.pojo.atom.enums.AtomStatusEnum +import com.tencent.devops.store.pojo.common.ATOM_POST_VERSION_TEST_FLAG_KEY_PREFIX +import com.tencent.devops.store.pojo.common.STORE_LATEST_TEST_FLAG_KEY_PREFIX +import com.tencent.devops.store.pojo.common.publication.StoreBuildResultRequest import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt index 2fa7c86fe295..f001714dd319 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/AtomReleaseServiceImpl.kt @@ -351,6 +351,7 @@ abstract class AtomReleaseServiceImpl @Autowired constructor() : AtomReleaseServ val branch = if (marketAtomUpdateRequest.branch.isNullOrBlank() || releaseType != ReleaseTypeEnum.HIS_VERSION_UPGRADE ) { + marketAtomUpdateRequest.branch = MASTER MASTER } else { marketAtomUpdateRequest.branch diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomClassifyServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomClassifyServiceImpl.kt index 9c9e56f45e40..c4c7b64259bb 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomClassifyServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomClassifyServiceImpl.kt @@ -56,7 +56,7 @@ import java.time.LocalDateTime */ @Suppress("ALL") @Service("ATOM_CLASSIFY_SERVICE") -class MarketAtomClassifyServiceImpl @Autowired constructor() : MarketAtomClassifyService, AbstractClassifyService() { +class MarketAtomClassifyServiceImpl : MarketAtomClassifyService, AbstractClassifyService() { private val logger = LoggerFactory.getLogger(MarketAtomClassifyServiceImpl::class.java) @@ -121,7 +121,7 @@ class MarketAtomClassifyServiceImpl @Autowired constructor() : MarketAtomClassif } } - override fun getDeleteClassifyFlag(classifyId: String): Boolean { + override fun getDeleteClassifyFlag(classifyId: String, storeType: StoreTypeEnum): Boolean { // 允许删除分类是条件:1、该分类下的原子插件都不处于上架状态 2、该分类下的原子插件如果处于下架中或者已下架状态但已经没人在用 var flag = false val releaseAtomNum = atomDao.countReleaseAtomNumByClassifyId(dslContext, classifyId) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomEnvServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomEnvServiceImpl.kt index 958f018f5c32..529e17bf0d41 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomEnvServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomEnvServiceImpl.kt @@ -46,6 +46,7 @@ import com.tencent.devops.store.atom.factory.AtomBusHandleFactory import com.tencent.devops.store.atom.service.AtomService import com.tencent.devops.store.atom.service.MarketAtomCommonService import com.tencent.devops.store.atom.service.MarketAtomEnvService +import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.common.dao.ClassifyDao import com.tencent.devops.store.common.dao.StoreProjectRelDao import com.tencent.devops.store.common.service.StoreI18nMessageService @@ -63,8 +64,8 @@ import com.tencent.devops.store.pojo.common.ATOM_POST_ENTRY_PARAM import com.tencent.devops.store.pojo.common.ATOM_POST_FLAG import com.tencent.devops.store.pojo.common.ATOM_POST_NORMAL_PROJECT_FLAG_KEY_PREFIX import com.tencent.devops.store.pojo.common.ATOM_POST_VERSION_TEST_FLAG_KEY_PREFIX -import com.tencent.devops.store.pojo.common.version.StoreVersion import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.version.StoreVersion import java.time.LocalDateTime import org.jooq.DSLContext import org.slf4j.LoggerFactory @@ -88,7 +89,8 @@ class MarketAtomEnvServiceImpl @Autowired constructor( private val atomService: AtomService, private val marketAtomCommonService: MarketAtomCommonService, private val storeI18nMessageService: StoreI18nMessageService, - private val redisOperation: RedisOperation + private val redisOperation: RedisOperation, + private val storeInnerPipelineConfig: StoreInnerPipelineConfig ) : MarketAtomEnvService { private val logger = LoggerFactory.getLogger(MarketAtomEnvServiceImpl::class.java) @@ -263,12 +265,15 @@ class MarketAtomEnvServiceImpl @Autowired constructor( AtomStatusEnum.UNDERCARRIAGING.status.toByte(), AtomStatusEnum.UNDERCARRIAGED.status.toByte() ) + val buildingFlag = + projectCode == storeInnerPipelineConfig.innerPipelineProject && atomStatus == AtomStatusEnum.BUILDING.status.toByte() val atomStatusList = getAtomStatusList( atomStatus = atomStatus, version = version, normalStatusList = normalStatusList, atomCode = atomCode, - projectCode = projectCode + projectCode = projectCode, + queryTestFlag = buildingFlag ) val atomDefaultFlag = marketAtomCommonService.isPublicAtom(atomCode) val atomBaseInfoRecord = marketAtomEnvInfoDao.getProjectAtomBaseInfo( @@ -277,7 +282,8 @@ class MarketAtomEnvServiceImpl @Autowired constructor( atomCode = atomCode, version = version, atomDefaultFlag = atomDefaultFlag, - atomStatusList = atomStatusList + atomStatusList = atomStatusList, + queryProjectFlag = !buildingFlag ) ?: throw ErrorCodeException( errorCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf("[project($projectCode)-plugin($atomCode)]") @@ -414,7 +420,8 @@ class MarketAtomEnvServiceImpl @Autowired constructor( version: String, normalStatusList: List, atomCode: String, - projectCode: String + projectCode: String, + queryTestFlag: Boolean ): List { return if (atomStatus != null) { mutableListOf(atomStatus) @@ -428,7 +435,13 @@ class MarketAtomEnvServiceImpl @Autowired constructor( this.add(AtomStatusEnum.RELEASED.status.toByte()) } } - val flag = storeProjectRelDao.isTestProjectCode(dslContext, atomCode, StoreTypeEnum.ATOM, projectCode) + val flag = queryTestFlag || + storeProjectRelDao.isTestProjectCode( + dslContext = dslContext, + storeCode = atomCode, + storeType = StoreTypeEnum.ATOM, + projectCode = projectCode + ) if (flag) { // 初始化项目或者调试项目有权查处于测试中、审核中的插件 this.addAll( diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomServiceImpl.kt index beb1966fbb27..965b75d15d79 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomServiceImpl.kt @@ -365,6 +365,7 @@ abstract class MarketAtomServiceImpl @Autowired constructor() : MarketAtomServic name = it[tAtom.NAME] as String, code = atomCode, version = it[tAtom.VERSION] as String, + status = AtomStatusEnum.getAtomStatus((it[tAtom.ATOM_STATUS] as Byte).toInt()), type = it[tAtom.JOB_TYPE] as String, rdType = AtomTypeEnum.getAtomType((it[tAtom.ATOM_TYPE] as Byte).toInt()), classifyCode = if (classifyMap.containsKey(classifyId)) classifyMap[classifyId] else "", diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomStatisticServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomStatisticServiceImpl.kt index 3b447413269a..8c3b9cfc600e 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomStatisticServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/MarketAtomStatisticServiceImpl.kt @@ -46,6 +46,7 @@ import com.tencent.devops.store.pojo.common.statistic.StoreStatisticPipelineNumU import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.atom.service.MarketAtomStatisticService import org.jooq.DSLContext +import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -233,20 +234,23 @@ class MarketAtomStatisticServiceImpl @Autowired constructor( dailyFailDetail = if (totalFailDetail != null) JsonUtil.toMap(totalFailDetail) else null, statisticsTime = startTime ) - if (storeDailyStatistic != null) { - storeStatisticDailyDao.updateDailyStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType, - storeDailyStatisticRequest = storeDailyStatisticRequest - ) - } else { - storeStatisticDailyDao.insertDailyStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType, - storeDailyStatisticRequest = storeDailyStatisticRequest - ) + dslContext.transaction { t -> + val context = DSL.using(t) + if (storeDailyStatistic != null) { + storeStatisticDailyDao.updateDailyStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType, + storeDailyStatisticRequest = storeDailyStatisticRequest + ) + } else { + storeStatisticDailyDao.insertDailyStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType, + storeDailyStatisticRequest = storeDailyStatisticRequest + ) + } } } page++ @@ -278,11 +282,14 @@ class MarketAtomStatisticServiceImpl @Autowired constructor( ) ) } - storeStatisticTotalDao.batchUpdatePipelineNum( - dslContext = dslContext, - pipelineNumUpdateList = pipelineNumUpdateList, - storeType = StoreTypeEnum.ATOM.type.toByte() - ) + dslContext.transaction { t -> + val context = DSL.using(t) + storeStatisticTotalDao.batchUpdatePipelineNum( + dslContext = context, + pipelineNumUpdateList = pipelineNumUpdateList, + storeType = StoreTypeEnum.ATOM.type.toByte() + ) + } } } page++ diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt index c2a8f3946c15..91708a5bcd87 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/atom/service/impl/OpAtomServiceImpl.kt @@ -327,6 +327,16 @@ class OpAtomServiceImpl @Autowired constructor( } else { approveReq.result == PASS } + // 入库信息,并设置当前版本的LATEST_FLAG + marketAtomDao.approveAtomFromOp( + dslContext = dslContext, + userId = userId, + atomId = atomId, + atomStatus = atomStatus, + approveReq = approveReq, + latestFlag = latestFlag, + pubTime = LocalDateTime.now() + ) if (passFlag) { atomReleaseService.handleAtomRelease( userId = userId, @@ -347,16 +357,6 @@ class OpAtomServiceImpl @Autowired constructor( // 发送通知消息 atomNotifyService.sendAtomReleaseAuditNotifyMessage(atomId, AuditTypeEnum.AUDIT_REJECT) } - // 入库信息,并设置当前版本的LATEST_FLAG - marketAtomDao.approveAtomFromOp( - dslContext = dslContext, - userId = userId, - atomId = atomId, - atomStatus = atomStatus, - approveReq = approveReq, - latestFlag = latestFlag, - pubTime = LocalDateTime.now() - ) // 更新默认插件缓存 if (approveReq.defaultFlag) { redisOperation.addSetValue(StoreUtils.getStorePublicFlagKey(StoreTypeEnum.ATOM.name), atomCode) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/configuration/StoreInnerPipelineConfig.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/configuration/StoreInnerPipelineConfig.kt index a46559346713..fab7880dec72 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/configuration/StoreInnerPipelineConfig.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/configuration/StoreInnerPipelineConfig.kt @@ -38,4 +38,10 @@ class StoreInnerPipelineConfig { @Value("\${store.innerPipeline.user:}") val innerPipelineUser: String = "" + + @Value("\${store.innerPipeline.gray.project:}") + val innerPipelineGrayProject: String = "" + + @Value("\${store.innerPipeline.gray.user:}") + val innerPipelineGrayUser: String = "" } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/AbstractStoreCommonDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/AbstractStoreCommonDao.kt index 2321e79dff1f..528524b1e82b 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/AbstractStoreCommonDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/AbstractStoreCommonDao.kt @@ -77,4 +77,9 @@ abstract class AbstractStoreCommonDao { ): StoreBaseInfo? abstract fun getStoreRepoHashIdByCode(dslContext: DSLContext, storeCode: String): String? + + abstract fun getStoreCodeById( + dslContext: DSLContext, + storeId: String + ): String? } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/MarketStoreQueryDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/MarketStoreQueryDao.kt index cd6f1bf55a95..e12c87bac83b 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/MarketStoreQueryDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/MarketStoreQueryDao.kt @@ -33,7 +33,6 @@ import com.tencent.devops.model.store.tables.TStoreBase import com.tencent.devops.model.store.tables.TStoreBaseFeature import com.tencent.devops.model.store.tables.TStoreCategoryRel import com.tencent.devops.model.store.tables.TStoreLabelRel -import com.tencent.devops.model.store.tables.TStoreProjectRel import com.tencent.devops.model.store.tables.TStoreStatisticsTotal import com.tencent.devops.store.pojo.common.KEY_STORE_CODE import com.tencent.devops.store.pojo.common.StoreInfoQuery @@ -130,6 +129,7 @@ class MarketStoreQueryDao { tStoreBase.STORE_TYPE, tStoreBase.NAME, tStoreBase.VERSION, + tStoreBase.STATUS, tStoreBase.DOCS_LINK, tStoreBase.DESCRIPTION, tStoreBase.SUMMARY, @@ -163,8 +163,9 @@ class MarketStoreQueryDao { val tStoreCategoryRel = TStoreCategoryRel.T_STORE_CATEGORY_REL val tClassify = TClassify.T_CLASSIFY val conditions = mutableListOf() + conditions.add(tStoreBase.STORE_TYPE.eq(storeType.type.toByte())) // 缩减查询范围 - if (storeInfoQuery.queryProjectComponentFlag) { + if (storeInfoQuery.getSpecQueryFlag()) { conditions.add( tStoreBase.ID.`in`( getStoreIdsByCondition( @@ -217,58 +218,26 @@ class MarketStoreQueryDao { storeInfoQuery: StoreInfoQuery ): SelectConditionStep> { val tStoreBase = TStoreBase.T_STORE_BASE - val tStoreBaseFeature = TStoreBaseFeature.T_STORE_BASE_FEATURE - val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL val selectJoinStep = dslContext.selectDistinct(tStoreBase.ID).from(tStoreBase) - val subConditions = mutableListOf() - subConditions.addAll(setStoreVisibleCondition(tStoreBase, tStoreBaseFeature, tStoreProjectRel, storeInfoQuery)) - subConditions.apply { - storeInfoQuery.projectCode?.let { - if (storeInfoQuery.queryProjectComponentFlag) { - selectJoinStep.leftJoin(tStoreBaseFeature).on( - tStoreBase.STORE_CODE.eq(tStoreBaseFeature.STORE_CODE) - .and(tStoreBase.STORE_TYPE.eq(tStoreBaseFeature.STORE_TYPE)) - ) - selectJoinStep.leftJoin(tStoreProjectRel).on( - tStoreBase.STORE_CODE.eq(tStoreProjectRel.STORE_CODE) - .and(tStoreBase.STORE_TYPE.eq(tStoreProjectRel.STORE_TYPE)) - ) - subConditions.add(tStoreProjectRel.PROJECT_CODE.eq(it).or(tStoreBaseFeature.PUBLIC_FLAG.eq(true))) - } - } - } - + val subConditions = setStoreVisibleCondition(tStoreBase, storeInfoQuery) return selectJoinStep.where(subConditions) } fun setStoreVisibleCondition( tStoreBase: TStoreBase, - tStoreBaseFeature: TStoreBaseFeature, - tStoreProjectRel: TStoreProjectRel, storeInfoQuery: StoreInfoQuery ): MutableList { val conditions = mutableListOf() val storeType = StoreTypeEnum.valueOf(storeInfoQuery.storeType) conditions.add(tStoreBase.STORE_TYPE.eq(storeType.type.toByte())) - if (storeInfoQuery.queryProjectComponentFlag) { - var testStoreQueryCondition = storeInfoQuery.testStoreCodes?.let { testStoreCodes -> + if (storeInfoQuery.getSpecQueryFlag()) { + val testStoreQueryCondition = storeInfoQuery.testStoreCodes?.let { testStoreCodes -> tStoreBase.STORE_CODE.`in`(testStoreCodes).and( - tStoreBase.STATUS.`in`( - listOf( - StoreStatusEnum.TESTING.name, - StoreStatusEnum.AUDITING.name - ) - ) - ) - } - - storeInfoQuery.projectCode?.let { projectCode -> - testStoreQueryCondition = testStoreQueryCondition?.and( - tStoreProjectRel.PROJECT_CODE.eq(projectCode).or(tStoreBaseFeature.PUBLIC_FLAG.eq(true)) + tStoreBase.STATUS.`in`(StoreStatusEnum.getTestStatusList()) ) } - var normalStoreQueryCondition = storeInfoQuery.normalStoreCodes?.let { normalStoreCodes -> + val normalStoreQueryCondition = storeInfoQuery.normalStoreCodes?.let { normalStoreCodes -> tStoreBase.STORE_CODE.`in`(normalStoreCodes).and( tStoreBase.STATUS.eq(StoreStatusEnum.RELEASED.name) ).and( @@ -276,18 +245,12 @@ class MarketStoreQueryDao { ) } - storeInfoQuery.projectCode?.let { projectCode -> - normalStoreQueryCondition = normalStoreQueryCondition?.and( - tStoreProjectRel.PROJECT_CODE.eq(projectCode).or(tStoreBaseFeature.PUBLIC_FLAG.eq(true)) - ) - } - if (testStoreQueryCondition != null && normalStoreQueryCondition != null) { - conditions.add(normalStoreQueryCondition!!.or(testStoreQueryCondition)) + conditions.add(normalStoreQueryCondition.or(testStoreQueryCondition)) } else if (testStoreQueryCondition != null) { - conditions.add(testStoreQueryCondition!!) + conditions.add(testStoreQueryCondition) } else if (normalStoreQueryCondition != null) { - conditions.add(normalStoreQueryCondition!!) + conditions.add(normalStoreQueryCondition) } } else { conditions.add(tStoreBase.STATUS.eq(StoreStatusEnum.RELEASED.name)) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseEnvExtQueryDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseEnvExtQueryDao.kt index a7f2dc8d7f3e..38f7a7dd9fb0 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseEnvExtQueryDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseEnvExtQueryDao.kt @@ -49,13 +49,14 @@ class StoreBaseEnvExtQueryDao { fun getBaseExtEnvsByEnvId( dslContext: DSLContext, envId: String, - fieldName: String? = null + vararg fieldNames: String? ): Result? { return with(TStoreBaseEnvExt.T_STORE_BASE_ENV_EXT) { val conditions = mutableListOf() conditions.add(ENV_ID.eq(envId)) - if (!fieldName.isNullOrBlank()) { - conditions.add(FIELD_NAME.eq(fieldName)) + val nonNullFieldNames = fieldNames.filterNotNull() // 过滤掉null值 + if (nonNullFieldNames.isNotEmpty()) { + conditions.add(FIELD_NAME.`in`(nonNullFieldNames)) } dslContext.selectFrom(this).where(conditions).fetch() } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseFeatureExtQueryDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseFeatureExtQueryDao.kt index 55a8174fdb2d..7543837fb6d4 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseFeatureExtQueryDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseFeatureExtQueryDao.kt @@ -59,12 +59,16 @@ class StoreBaseFeatureExtQueryDao { fun queryStoreBaseFeatureExt( dslContext: DSLContext, storeCode: String, - storeType: StoreTypeEnum + storeType: StoreTypeEnum, + fieldNames: Set? = null ): Result { with(TStoreBaseFeatureExt.T_STORE_BASE_FEATURE_EXT) { val conditions = mutableListOf() conditions.add(STORE_TYPE.eq(storeType.type.toByte())) conditions.add(STORE_CODE.eq(storeCode)) + if (!fieldNames.isNullOrEmpty()) { + conditions.add(FIELD_NAME.`in`(fieldNames)) + } return dslContext.selectFrom(this) .where(conditions) .fetch() diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseQueryDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseQueryDao.kt index 346e05033fe1..112b1c71d61c 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseQueryDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreBaseQueryDao.kt @@ -139,12 +139,9 @@ class StoreBaseQueryDao { return with(TStoreBase.T_STORE_BASE) { val conditions = mutableListOf(STORE_CODE.`in`(storeCodes)) conditions.add(STORE_TYPE.eq(storeType.type.toByte())) - val testStatusEnumList = listOf( - StoreStatusEnum.TESTING, - StoreStatusEnum.AUDITING - ) + val testStatusList = StoreStatusEnum.getTestStatusList() if (testComponentFlag) { - conditions.add(STATUS.`in`(testStatusEnumList)) + conditions.add(STATUS.`in`(testStatusList)) val subQuery = dslContext.select( STORE_CODE, STORE_TYPE, @@ -160,10 +157,11 @@ class StoreBaseQueryDao { .and(CREATE_TIME.eq(subQuery.field(KEY_CREATE_TIME, LocalDateTime::class.java))) ) .where(conditions) + .skipCheck() .fetch() } else { conditions.add(LATEST_FLAG.eq(true)) - conditions.add(STATUS.notIn(testStatusEnumList)) + conditions.add(STATUS.notIn(testStatusList)) dslContext.selectFrom(this) .where(conditions) .fetch() @@ -246,7 +244,8 @@ class StoreBaseQueryDao { storeType: StoreTypeEnum, name: String? = null, storeCode: String? = null, - status: StoreStatusEnum? = null + status: StoreStatusEnum? = null, + classifyId: String? = null ): Int { with(TStoreBase.T_STORE_BASE) { val conditions = mutableListOf() @@ -257,9 +256,12 @@ class StoreBaseQueryDao { if (!storeCode.isNullOrBlank()) { conditions.add(STORE_CODE.eq(storeCode)) } - if (status != null) { + status?.let { conditions.add(STATUS.eq(status.name)) } + classifyId?.let { + conditions.add(CLASSIFY_ID.eq(it)) + } return dslContext.selectCount().from(this).where(conditions).fetchOne(0, Int::class.java)!! } } @@ -276,9 +278,9 @@ class StoreBaseQueryDao { fun countComponents( dslContext: DSLContext, queryComponentsParam: QueryComponentsParam, - classifyId: String?, - categoryIds: List?, - labelIds: List? + classifyId: String? = null, + categoryIds: List? = null, + labelIds: List? = null ): Int { val tStoreBase = TStoreBase.T_STORE_BASE val baseStep = dslContext.selectCount().from(tStoreBase) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreDeptRelDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreDeptRelDao.kt index 41fe6e7dd759..0a664c846842 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreDeptRelDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreDeptRelDao.kt @@ -30,8 +30,8 @@ package com.tencent.devops.store.common.dao import com.tencent.devops.common.api.util.UUIDUtil import com.tencent.devops.model.store.tables.TStoreDeptRel import com.tencent.devops.model.store.tables.records.TStoreDeptRelRecord -import com.tencent.devops.store.pojo.common.DeptInfo import com.tencent.devops.store.pojo.common.enums.DeptStatusEnum +import com.tencent.devops.store.pojo.common.visible.DeptInfo import org.jooq.Condition import org.jooq.DSLContext import org.jooq.Result @@ -46,22 +46,26 @@ class StoreDeptRelDao { dslContext: DSLContext, storeCode: String, storeType: Byte, - deptStatus: DeptStatusEnum? = null, + deptStatusList: List? = null, deptIdList: List? = null ): Result? { with(TStoreDeptRel.T_STORE_DEPT_REL) { val conditions = mutableListOf() conditions.add(STORE_CODE.eq(storeCode)) conditions.add(STORE_TYPE.eq(storeType)) - if (null != deptStatus) conditions.add(STATUS.eq(deptStatus.status.toByte())) - if (null != deptIdList) conditions.add(DEPT_ID.`in`(deptIdList)) + if (!deptStatusList.isNullOrEmpty()) conditions.add(STATUS.`in`(deptStatusList)) + if (!deptIdList.isNullOrEmpty()) conditions.add(DEPT_ID.`in`(deptIdList)) return dslContext.selectFrom(this) .where(conditions) .fetch() } } - fun batchList(dslContext: DSLContext, storeCodeList: Collection, storeType: Byte): Result? { + fun batchList( + dslContext: DSLContext, + storeCodeList: Collection, + storeType: Byte + ): Result? { with(TStoreDeptRel.T_STORE_DEPT_REL) { return dslContext.selectFrom(this) .where(STORE_CODE.`in`(storeCodeList)) @@ -80,11 +84,18 @@ class StoreDeptRelDao { ) { with(TStoreDeptRel.T_STORE_DEPT_REL) { val addStep = deptInfoList.map { + val deptStatus = if (it.status.isNullOrBlank()) { + DeptStatusEnum.APPROVED.status.toByte() + } else { + DeptStatusEnum.valueOf(it.status!!).status.toByte() + } dslContext.insertInto(this, ID, STORE_CODE, DEPT_ID, DEPT_NAME, + STATUS, + COMMENT, STORE_TYPE, CREATOR, MODIFIER @@ -94,13 +105,15 @@ class StoreDeptRelDao { storeCode, it.deptId, it.deptName, + deptStatus, + it.comment, storeType, userId, userId ) .onDuplicateKeyUpdate() - .set(STATUS, DeptStatusEnum.APPROVING.status.toByte()) // 如果添加的机构之前被审核拒绝过,则会将之前拒绝过的记录更新为待审核状态 - .set(COMMENT, "") + .set(STATUS, deptStatus) + .set(COMMENT, it.comment) .set(MODIFIER, userId) .set(UPDATE_TIME, LocalDateTime.now()) } @@ -108,73 +121,6 @@ class StoreDeptRelDao { } } - fun batchAdd( - dslContext: DSLContext, - userId: String, - storeCode: String, - deptInfoList: List, - status: Byte, - comment: String, - storeType: Byte - ): IntArray? { - with(TStoreDeptRel.T_STORE_DEPT_REL) { - val addStep = deptInfoList.map { - dslContext.insertInto(this, - ID, - STORE_CODE, - DEPT_ID, - DEPT_NAME, - STORE_TYPE, - CREATOR, - MODIFIER, - STATUS, - COMMENT, - UPDATE_TIME - ) - .values( - UUIDUtil.generate(), - storeCode, - it.deptId, - it.deptName, - storeType, - userId, - userId, - status, - comment, - LocalDateTime.now() - ) - .onDuplicateKeyUpdate() - .set(STATUS, status) - .set(COMMENT, comment) - .set(MODIFIER, userId) - .set(UPDATE_TIME, LocalDateTime.now()) - } - return dslContext.batch(addStep).execute() - } - } - - fun batchUpdate( - dslContext: DSLContext, - userId: String, - storeCode: String, - deptIdList: List, - status: Byte, - comment: String, - storeType: Byte - ) { - with(TStoreDeptRel.T_STORE_DEPT_REL) { - dslContext.update(this) - .set(STATUS, status) - .set(COMMENT, comment) - .set(MODIFIER, userId) - .set(UPDATE_TIME, LocalDateTime.now()) - .where(STORE_CODE.eq(storeCode)) - .and(DEPT_ID.`in`(deptIdList)) - .and(STORE_TYPE.eq(storeType)) - .execute() - } - } - fun delete(dslContext: DSLContext, id: String) { with(TStoreDeptRel.T_STORE_DEPT_REL) { dslContext.deleteFrom(this) @@ -212,4 +158,24 @@ class StoreDeptRelDao { .fetchOne(0, Int::class.java)!! } } + + fun updateDeptStatus( + dslContext: DSLContext, + storeCode: String, + storeType: Byte, + originStatus: Byte, + newStatus: Byte, + userId: String + ) { + with(TStoreDeptRel.T_STORE_DEPT_REL) { + dslContext.update(this) + .set(STATUS, newStatus) + .set(MODIFIER, userId) + .set(UPDATE_TIME, LocalDateTime.now()) + .where(STORE_CODE.eq(storeCode)) + .and(STORE_TYPE.eq(storeType)) + .and(STATUS.eq(originStatus)) + .execute() + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StorePipelineRelDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StorePipelineRelDao.kt index 3ac547fff815..82248cc78e10 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StorePipelineRelDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StorePipelineRelDao.kt @@ -32,9 +32,11 @@ import com.tencent.devops.model.store.tables.TStorePipelineRel import com.tencent.devops.model.store.tables.records.TStorePipelineRelRecord import com.tencent.devops.store.pojo.common.enums.StorePipelineBusTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import java.time.LocalDateTime +import org.jooq.Condition import org.jooq.DSLContext +import org.jooq.Result import org.springframework.stereotype.Repository -import java.time.LocalDateTime @Repository class StorePipelineRelDao { @@ -92,6 +94,30 @@ class StorePipelineRelDao { } } + fun getStorePipelineRelByStoreCode( + dslContext: DSLContext, + storeCode: String, + storeType: StoreTypeEnum + ): TStorePipelineRelRecord? { + with(TStorePipelineRel.T_STORE_PIPELINE_REL) { + return dslContext.selectFrom(this) + .where(STORE_CODE.eq(storeCode)) + .and(STORE_TYPE.eq(storeType.type.toByte())) + .orderBy(UPDATE_TIME.desc()) + .fetchOne() + } + } + + fun getStoreTypeByLatestPipelineId(dslContext: DSLContext, pipelineId: String): Byte? { + with(TStorePipelineRel.T_STORE_PIPELINE_REL) { + return dslContext.select(STORE_TYPE).from(this) + .where(PIPELINE_ID.eq(pipelineId)) + .orderBy(UPDATE_TIME.desc()) + .limit(1) + .fetchOne(0, Byte::class.java) + } + } + fun deleteStorePipelineRel(dslContext: DSLContext, storeCode: String, storeType: Byte) { with(TStorePipelineRel.T_STORE_PIPELINE_REL) { dslContext.deleteFrom(this) @@ -115,6 +141,23 @@ class StorePipelineRelDao { } } + fun updateStorePipelineProject( + dslContext: DSLContext, + storeCode: String, + storeType: StoreTypeEnum, + pipelineId: String, + projectCode: String + ) { + with(TStorePipelineRel.T_STORE_PIPELINE_REL) { + dslContext.update(this) + .set(PROJECT_CODE, projectCode) + .set(PIPELINE_ID, pipelineId) + .set(UPDATE_TIME, LocalDateTime.now()) + .where(STORE_CODE.eq(storeCode).and(STORE_TYPE.eq(storeType.type.toByte()))) + .execute() + } + } + fun deleteStorePipelineRelById(dslContext: DSLContext, id: String) { with(TStorePipelineRel.T_STORE_PIPELINE_REL) { dslContext.deleteFrom(this) @@ -122,4 +165,27 @@ class StorePipelineRelDao { .execute() } } + + fun getStorePipelineRelRecords( + dslContext: DSLContext, + limit: Int, + offset: Int, + storeType: StoreTypeEnum? = null, + storeCode: String? = null + ): Result? { + return with(TStorePipelineRel.T_STORE_PIPELINE_REL) { + val conditions = mutableListOf() + storeType?.let { + conditions.add(STORE_TYPE.eq(storeType.type.toByte())) + } + storeCode?.let { + conditions.add(STORE_CODE.eq(storeCode)) + } + dslContext.selectFrom(this) + .where(conditions) + .orderBy(CREATE_TIME.asc(), ID.asc()) + .limit(limit).offset(offset) + .fetch() + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreProjectRelDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreProjectRelDao.kt index a4c2a4833dc5..73270a968f65 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreProjectRelDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/dao/StoreProjectRelDao.kt @@ -153,7 +153,7 @@ class StoreProjectRelDao { return dslContext.selectCount().from(this) .where(STORE_CODE.eq(storeCode) .and(STORE_TYPE.eq(storeType)) - .and(TYPE.eq(1)) + .and(TYPE.eq(StoreProjectTypeEnum.COMMON.type.toByte())) ) .fetchOne(0, Int::class.java)!! } @@ -266,8 +266,7 @@ class StoreProjectRelDao { storeProjectTypes?.let { conditions.add(TYPE.`in`(storeProjectTypes)) } - // 测试中的应用对应的调试项目下无需判断测试版本的应用已安装 - if (!instanceId.isNullOrBlank() && storeProjectTypes?.contains(StoreProjectTypeEnum.TEST.type.toByte()) != true) { + if (!instanceId.isNullOrBlank()) { conditions.add(INSTANCE_ID.eq(instanceId)) } val baseQuery = dslContext.select(STORE_CODE, VERSION) @@ -386,17 +385,21 @@ class StoreProjectRelDao { storeCode: String, storeType: StoreTypeEnum ): String? { - val a = TStoreMember.T_STORE_MEMBER.`as`("a") - val b = TStoreProjectRel.T_STORE_PROJECT_REL.`as`("b") - val finalStep = dslContext.select(b.PROJECT_CODE) - .from(a) - .join(b) - .on(a.STORE_CODE.eq(b.STORE_CODE).and(a.STORE_TYPE.eq(b.STORE_TYPE))) - .where(a.USERNAME.eq(userId)) - .and(b.STORE_CODE.eq(storeCode)) - .and(b.TYPE.eq(StoreProjectTypeEnum.TEST.type.toByte())) - .and(b.CREATOR.eq(userId)) - .and(a.STORE_TYPE.eq(storeType.type.toByte())) + val tStoreMember = TStoreMember.T_STORE_MEMBER + val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL + val finalStep = dslContext.select(tStoreProjectRel.PROJECT_CODE) + .from(tStoreMember) + .join(tStoreProjectRel) + .on( + tStoreMember.STORE_CODE.eq(tStoreProjectRel.STORE_CODE) + .and(tStoreMember.STORE_TYPE.eq(tStoreProjectRel.STORE_TYPE)) + ) + .where(tStoreMember.USERNAME.eq(userId)) + .and(tStoreProjectRel.STORE_CODE.eq(storeCode)) + .and(tStoreProjectRel.TYPE.eq(StoreProjectTypeEnum.TEST.type.toByte())) + .and(tStoreProjectRel.CREATOR.eq(userId)) + .and(tStoreMember.STORE_TYPE.eq(storeType.type.toByte())) + .limit(1) return finalStep.fetchOne(0, String::class.java) } @@ -458,22 +461,24 @@ class StoreProjectRelDao { } /** - * 删除用户的组件设定的调试项目 + * 删除组件的项目 */ - fun deleteUserStoreTestProject( + fun deleteStoreProject( dslContext: DSLContext, - userId: String, storeProjectType: StoreProjectTypeEnum, storeCode: String, - storeType: StoreTypeEnum + storeType: StoreTypeEnum, + userId: String? = null ) { with(TStoreProjectRel.T_STORE_PROJECT_REL) { - dslContext.deleteFrom(this) - .where(CREATOR.eq(userId)) - .and(TYPE.eq(storeProjectType.type.toByte())) - .and(STORE_CODE.eq(storeCode)) - .and(STORE_TYPE.eq(storeType.type.toByte())) - .execute() + val conditions = mutableListOf() + conditions.add(STORE_CODE.eq(storeCode)) + conditions.add(STORE_TYPE.eq(storeType.type.toByte())) + conditions.add(TYPE.eq(storeProjectType.type.toByte())) + if (userId != null) { + conditions.add(CREATOR.eq(userId)) + } + dslContext.deleteFrom(this).where(conditions).execute() } } @@ -577,18 +582,20 @@ class StoreProjectRelDao { } } - fun getInitProjectInfoByStoreCode( + fun getProjectInfoByStoreCode( dslContext: DSLContext, storeCode: String, - storeType: Byte - ): TStoreProjectRelRecord? { + storeType: Byte, + storeProjectType: StoreProjectTypeEnum? = null + ): Result? { with(TStoreProjectRel.T_STORE_PROJECT_REL) { - return dslContext.selectFrom(this) - .where(STORE_CODE.eq(storeCode) - .and(STORE_TYPE.eq(storeType)) - .and(TYPE.eq(StoreProjectTypeEnum.INIT.type.toByte())) - ) - .fetchOne() + val conditions = mutableListOf() + conditions.add(STORE_CODE.eq(storeCode)) + conditions.add(STORE_TYPE.eq(storeType)) + if (storeProjectType != null) { + conditions.add(TYPE.eq(storeProjectType.type.toByte())) + } + return dslContext.selectFrom(this).where(conditions).fetch() } } @@ -608,7 +615,28 @@ class StoreProjectRelDao { conditions.add(TYPE.eq(StoreProjectTypeEnum.TEST.type.toByte())) return dslContext.selectFrom(this) .where(conditions) + .limit(1) .fetchOne() } } + + /** + * 判断组件是否被用户安装 + */ + fun isInstalledByUser( + dslContext: DSLContext, + userId: String, + storeCode: String, + storeType: Byte + ): Boolean { + with(TStoreProjectRel.T_STORE_PROJECT_REL) { + return dslContext.selectCount() + .from(this) + .where(CREATOR.eq(userId)) + .and(STORE_CODE.eq(storeCode)) + .and(STORE_TYPE.eq(storeType)) + .and(TYPE.eq(StoreProjectTypeEnum.COMMON.type.toByte())) + .fetchOne(0, Long::class.java) != 0L + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreCreatePreBusHandler.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreCreatePreBusHandler.kt new file mode 100644 index 000000000000..3fe204727d06 --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreCreatePreBusHandler.kt @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.handler + +import com.tencent.devops.store.common.service.StoreBaseCreateService +import com.tencent.devops.store.pojo.common.handler.Handler +import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest +import org.springframework.stereotype.Service + +@Service +class StoreCreatePreBusHandler( + private val storeBaseCreateService: StoreBaseCreateService +) : Handler { + + override fun canExecute(handlerRequest: StoreCreateRequest): Boolean { + return true + } + + override fun execute(handlerRequest: StoreCreateRequest) { + // 执行前置业务 + storeBaseCreateService.doStoreCreatePreBus(handlerRequest) + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreDeleteCodeRepositoryHandler.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreDeleteCodeRepositoryHandler.kt new file mode 100644 index 000000000000..21f3f8c3e906 --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreDeleteCodeRepositoryHandler.kt @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.handler + +import com.tencent.devops.store.common.service.StoreBaseDeleteService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.handler.Handler +import com.tencent.devops.store.pojo.common.publication.StoreDeleteRequest +import org.springframework.stereotype.Service + +@Service +class StoreDeleteCodeRepositoryHandler( + private val storeBaseDeleteService: StoreBaseDeleteService +) : Handler { + + override fun canExecute(handlerRequest: StoreDeleteRequest): Boolean { + + return when (handlerRequest.storeType) { + StoreTypeEnum.TEMPLATE.name, StoreTypeEnum.IMAGE.name -> false + else -> true + } + } + + override fun execute(handlerRequest: StoreDeleteRequest) { + // 删除组件代码库 + storeBaseDeleteService.deleteComponentCodeRepository(handlerRequest) + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreUpdatePreBusHandler.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreUpdatePreBusHandler.kt new file mode 100644 index 000000000000..42e8c99c0d86 --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/handler/StoreUpdatePreBusHandler.kt @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.handler + +import com.tencent.devops.store.common.service.StoreBaseUpdateService +import com.tencent.devops.store.pojo.common.handler.Handler +import com.tencent.devops.store.pojo.common.publication.StoreUpdateRequest +import org.springframework.stereotype.Service + +@Service +class StoreUpdatePreBusHandler( + private val storeBaseUpdateService: StoreBaseUpdateService +) : Handler { + + override fun canExecute(handlerRequest: StoreUpdateRequest): Boolean { + return true + } + + override fun execute(handlerRequest: StoreUpdateRequest) { + // 执行前置业务 + storeBaseUpdateService.doStoreUpdatePreBus(handlerRequest) + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/BuildStoreResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/BuildStoreResourceImpl.kt index 1dc6edf00a6d..ac8c556b4380 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/BuildStoreResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/BuildStoreResourceImpl.kt @@ -62,6 +62,7 @@ class BuildStoreResourceImpl @Autowired constructor( } override fun getStorePkgRunEnvInfo( + devopsEnv: String?, storeType: StoreTypeEnum, language: String, runtimeVersion: String, @@ -70,6 +71,7 @@ class BuildStoreResourceImpl @Autowired constructor( ): Result { return Result( storePkgRunEnvInfoService.getStorePkgRunEnvInfo( + devopsEnv = devopsEnv, userId = "", storeType = storeType, language = language, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/OpStoreResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/OpStoreResourceImpl.kt new file mode 100644 index 000000000000..8257089585bd --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/OpStoreResourceImpl.kt @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.resources + +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.store.api.common.OpStoreResource +import com.tencent.devops.store.common.service.StorePipelineService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class OpStoreResourceImpl @Autowired constructor( + private val storePipelineService: StorePipelineService +) : OpStoreResource { + + override fun deleteStoreInnerPipeline( + userId: String, + storeType: StoreTypeEnum?, + storeCode: String?, + excludeProjectCode: String? + ): Result { + return Result( + storePipelineService.deleteStoreInnerPipeline( + userId = userId, + storeType = storeType, + storeCode = storeCode, + excludeProjectCode = excludeProjectCode + ) + ) + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreComponentResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreComponentResourceImpl.kt index 5bd44d18e536..639bc126bc76 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreComponentResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreComponentResourceImpl.kt @@ -126,6 +126,7 @@ class ServiceStoreComponentResourceImpl @Autowired constructor( queryProjectComponentFlag: Boolean, sortType: StoreSortTypeEnum?, instanceId: String?, + queryTestFlag: Boolean?, page: Int, pageSize: Int ): Result> { @@ -145,6 +146,7 @@ class ServiceStoreComponentResourceImpl @Autowired constructor( queryProjectComponentFlag = queryProjectComponentFlag, sortType = sortType, instanceId = instanceId, + queryTestFlag = queryTestFlag, page = page, pageSize = pageSize, installed = installed, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreResourceImpl.kt index 949c443ed937..7aa0c4f49c35 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/ServiceStoreResourceImpl.kt @@ -30,6 +30,7 @@ package com.tencent.devops.store.common.resources import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.web.RestResource import com.tencent.devops.store.api.common.ServiceStoreResource +import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.common.service.ClassifyService import com.tencent.devops.store.common.service.StoreBuildService import com.tencent.devops.store.common.service.StoreComponentManageService @@ -53,7 +54,8 @@ class ServiceStoreResourceImpl @Autowired constructor( private val storeErrorCodeService: StoreErrorCodeService, private val storeMemberService: StoreMemberService, private val classifyService: ClassifyService, - private val storeComponentManageService: StoreComponentManageService + private val storeComponentManageService: StoreComponentManageService, + private val storeInnerPipelineConfig: StoreInnerPipelineConfig ) : ServiceStoreResource { override fun uninstall(storeCode: String, storeType: StoreTypeEnum, projectCode: String): Result { @@ -80,6 +82,24 @@ class ServiceStoreResourceImpl @Autowired constructor( ) } + override fun isPublicProject(projectCode: String): Result { + return Result( + projectCode == storeInnerPipelineConfig.innerPipelineProject + ) + } + + override fun validatePipelineUserStorePermission( + storeCode: String, + storeType: StoreTypeEnum, + userId: String + ): Result { + return Result( + storeInnerPipelineConfig.innerPipelineUser == userId || storeMemberService.isStoreMember( + userId, storeCode, storeType.type.toByte() + ) + ) + } + override fun isComplianceErrorCode( storeCode: String, storeType: StoreTypeEnum, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentManageResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentManageResourceImpl.kt index 793ee411624f..e9f02dc846e8 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentManageResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentManageResourceImpl.kt @@ -31,15 +31,19 @@ import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.pipeline.enums.ChannelCode import com.tencent.devops.common.web.RestResource import com.tencent.devops.store.api.common.UserStoreComponentManageResource -import com.tencent.devops.store.common.service.impl.StoreComponentManageServiceImpl +import com.tencent.devops.store.common.service.StoreComponentManageService +import com.tencent.devops.store.common.service.StoreProjectService import com.tencent.devops.store.pojo.common.InstallStoreReq import com.tencent.devops.store.pojo.common.StoreBaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.UnInstallReq +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreDeleteRequest +import com.tencent.devops.store.pojo.common.test.StoreTestRequest @RestResource class UserStoreComponentManageResourceImpl( - private val storeComponentManageService: StoreComponentManageServiceImpl + private val storeComponentManageService: StoreComponentManageService, + private val storeProjectService: StoreProjectService ) : UserStoreComponentManageResource { override fun updateComponentBaseInfo( @@ -86,4 +90,28 @@ class UserStoreComponentManageResourceImpl( handlerRequest = StoreDeleteRequest(storeCode, storeType) ) } + + override fun updateStoreRepositoryAuthorizer( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Result { + return storeComponentManageService.updateStoreRepositoryAuthorizer(userId, storeType, storeCode) + } + + override fun saveStoreTestInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String, + storeTestRequest: StoreTestRequest + ): Result { + return Result( + storeProjectService.saveStoreTestInfo( + userId = userId, + storeType = storeType, + storeCode = storeCode, + storeTestRequest = storeTestRequest + ) + ) + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentQueryResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentQueryResourceImpl.kt index bcea6fd49fc2..1a2b5c9e36e2 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentQueryResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreComponentQueryResourceImpl.kt @@ -32,6 +32,8 @@ import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.web.RestResource import com.tencent.devops.store.api.common.UserStoreComponentQueryResource import com.tencent.devops.store.common.service.StoreComponentQueryService +import com.tencent.devops.store.common.service.StoreMediaService +import com.tencent.devops.store.common.service.StoreProjectService import com.tencent.devops.store.pojo.common.MarketItem import com.tencent.devops.store.pojo.common.MarketMainItem import com.tencent.devops.store.pojo.common.MyStoreComponent @@ -40,13 +42,17 @@ import com.tencent.devops.store.pojo.common.StoreInfoQuery import com.tencent.devops.store.pojo.common.enums.RdTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreSortTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.media.StoreMediaInfo +import com.tencent.devops.store.pojo.common.test.StoreTestItem import com.tencent.devops.store.pojo.common.version.StoreDeskVersionItem import com.tencent.devops.store.pojo.common.version.StoreShowVersionInfo import org.springframework.beans.factory.annotation.Autowired @RestResource class UserStoreComponentQueryResourceImpl @Autowired constructor( - private val storeComponentQueryService: StoreComponentQueryService + private val storeComponentQueryService: StoreComponentQueryService, + private val storeProjectService: StoreProjectService, + private val storeMediaService: StoreMediaService ) : UserStoreComponentQueryResource { override fun getMyComponents( @@ -151,6 +157,7 @@ class UserStoreComponentQueryResourceImpl @Autowired constructor( queryProjectComponentFlag: Boolean, sortType: StoreSortTypeEnum?, instanceId: String?, + queryTestFlag: Boolean?, page: Int, pageSize: Int ): Result> { @@ -170,6 +177,7 @@ class UserStoreComponentQueryResourceImpl @Autowired constructor( queryProjectComponentFlag = queryProjectComponentFlag, sortType = sortType, instanceId = instanceId, + queryTestFlag = queryTestFlag, page = page, pageSize = pageSize, installed = installed, @@ -193,4 +201,22 @@ class UserStoreComponentQueryResourceImpl @Autowired constructor( ) ) } + + override fun getStoreTestInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Result> { + return Result( + storeProjectService.getStoreTestInfo(userId = userId, storeType = storeType, storeCode = storeCode) + ) + } + + override fun getStoreMediaInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Result?> { + return storeMediaService.getByCode(storeCode, storeType) + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreReleaseResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreReleaseResourceImpl.kt index c605576586dd..448094044f8c 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreReleaseResourceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreReleaseResourceImpl.kt @@ -31,6 +31,7 @@ import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.web.RestResource import com.tencent.devops.store.api.common.UserStoreReleaseResource import com.tencent.devops.store.common.service.StoreReleaseService +import com.tencent.devops.store.pojo.common.StoreReleaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateResponse import com.tencent.devops.store.pojo.common.publication.StoreOfflineRequest @@ -70,6 +71,14 @@ class UserStoreReleaseResourceImpl @Autowired constructor( return Result(storeReleaseService.passTest(userId, storeId)) } + override fun editReleaseInfo( + userId: String, + storeId: String, + storeReleaseInfoUpdateRequest: StoreReleaseInfoUpdateRequest + ): Result { + return Result(storeReleaseService.editReleaseInfo(userId, storeId, storeReleaseInfoUpdateRequest)) + } + override fun offlineComponent(userId: String, storeOfflineRequest: StoreOfflineRequest): Result { return Result(storeReleaseService.offlineComponent(userId, storeOfflineRequest)) } @@ -77,4 +86,8 @@ class UserStoreReleaseResourceImpl @Autowired constructor( override fun rebuild(userId: String, storeId: String): Result { return Result(storeReleaseService.rebuild(userId, storeId)) } + + override fun back(userId: String, storeId: String): Result { + return Result(storeReleaseService.back(userId, storeId)) + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreVisibleDeptResourceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreVisibleDeptResourceImpl.kt new file mode 100644 index 000000000000..a61167261bbd --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/resources/UserStoreVisibleDeptResourceImpl.kt @@ -0,0 +1,52 @@ +package com.tencent.devops.store.common.resources + +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.web.RestResource +import com.tencent.devops.store.api.common.UserStoreVisibleDeptResource +import com.tencent.devops.store.common.service.StoreVisibleDeptService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptReq +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptResp +import org.springframework.beans.factory.annotation.Autowired + +@RestResource +class UserStoreVisibleDeptResourceImpl @Autowired constructor( + private val storeVisibleDeptService: StoreVisibleDeptService +) : UserStoreVisibleDeptResource { + + override fun addVisibleDept(userId: String, storeVisibleDeptReq: StoreVisibleDeptReq): Result { + return storeVisibleDeptService.addVisibleDept( + userId = userId, + storeCode = storeVisibleDeptReq.storeCode, + storeType = StoreTypeEnum.valueOf(storeVisibleDeptReq.storeType), + deptInfos = storeVisibleDeptReq.deptInfos + ) + } + + override fun getVisibleDept( + userId: String, + storeType: String, + storeCode: String, + deptStatusInfos: String? + ): Result { + return storeVisibleDeptService.getVisibleDept( + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType), + deptStatusInfos = deptStatusInfos + ) + } + + override fun deleteVisibleDept( + userId: String, + storeType: String, + storeCode: String, + deptIds: String + ): Result { + return storeVisibleDeptService.deleteVisibleDept( + userId = userId, + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType), + deptIds = deptIds + ) + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractClassifyService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractClassifyService.kt index 7ae5bc587290..cbf30e3195e5 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractClassifyService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractClassifyService.kt @@ -27,11 +27,13 @@ package com.tencent.devops.store.common.service +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum + @Suppress("ALL") abstract class AbstractClassifyService { /** * 获取删除分类标识 */ - abstract fun getDeleteClassifyFlag(classifyId: String): Boolean + abstract fun getDeleteClassifyFlag(classifyId: String, storeType: StoreTypeEnum): Boolean } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractDomainService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractDomainService.kt new file mode 100644 index 000000000000..7a5c0d9bc389 --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/AbstractDomainService.kt @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.service + +abstract class AbstractDomainService { + + /** + * 转换url域名 + * @param url url地址 + * @return 转换域名后的url地址 + */ + abstract fun convertDomain(url: String): String +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseCreateService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseCreateService.kt index 4ec746c7d19e..7bb31ae33092 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseCreateService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseCreateService.kt @@ -39,6 +39,14 @@ interface StoreBaseCreateService { storeCreateRequest: StoreCreateRequest ) + /** + * 执行新增组件请求前置业务 + * @param storeCreateRequest 新增组件请求报文 + */ + fun doStoreCreatePreBus( + storeCreateRequest: StoreCreateRequest + ) + /** * 持久化新增组件数据 * @param storeCreateRequest 新增组件请求报文 diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseDeleteService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseDeleteService.kt index bedd3704003f..1eb9039bc0f9 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseDeleteService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseDeleteService.kt @@ -43,6 +43,12 @@ interface StoreBaseDeleteService { */ fun deleteComponentRepoFile(handlerRequest: StoreDeleteRequest) + /** + * 删除组件代码库 + * @param handlerRequest 删除组件请求报文体 + */ + fun deleteComponentCodeRepository(handlerRequest: StoreDeleteRequest) + /** * 持久化删除组件数据 * @param handlerRequest 删除组件请求报文体 diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseUpdateService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseUpdateService.kt index c3c33ffd8b7d..53c791141187 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseUpdateService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreBaseUpdateService.kt @@ -47,6 +47,14 @@ interface StoreBaseUpdateService { storeUpdateRequest: StoreUpdateRequest ) + /** + * 执行更新组件请求前置业务 + * @param storeUpdateRequest 更新组件请求报文 + */ + fun doStoreUpdatePreBus( + storeUpdateRequest: StoreUpdateRequest + ) + /** * 持久化更新组件数据 * @param storeUpdateRequest 更新组件请求报文 diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreCommonService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreCommonService.kt index d667307c6e3a..785cf6a2af65 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreCommonService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreCommonService.kt @@ -27,12 +27,12 @@ package com.tencent.devops.store.common.service -import com.tencent.devops.store.pojo.common.publication.ReleaseProcessItem -import com.tencent.devops.store.pojo.common.publication.StoreProcessInfo -import com.tencent.devops.store.pojo.common.version.StoreShowVersionInfo import com.tencent.devops.store.pojo.common.enums.ReleaseTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.publication.ReleaseProcessItem +import com.tencent.devops.store.pojo.common.publication.StoreProcessInfo +import com.tencent.devops.store.pojo.common.version.StoreShowVersionInfo import com.tencent.devops.store.pojo.common.version.VersionModel import org.jooq.DSLContext @@ -50,6 +50,14 @@ interface StoreCommonService { storeType: StoreTypeEnum ): String + /** + * 根据ID获取组件名称 + */ + fun getStoreCodeById( + storeId: String, + storeType: StoreTypeEnum + ): String + /** * 根据标识获取组件公共标识 */ diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreComponentManageService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreComponentManageService.kt index 4f8da4b0da93..1b35cbb9ae41 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreComponentManageService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreComponentManageService.kt @@ -68,4 +68,13 @@ interface StoreComponentManageService { version: String, installedPkgFileShaContentRequest: InstalledPkgFileShaContentRequest ): Result + + /** + * 更改组件授权人信息 + */ + fun updateStoreRepositoryAuthorizer( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Result } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreDeptService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreDeptService.kt index ac777637c8d1..4b3566b08e03 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreDeptService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreDeptService.kt @@ -28,7 +28,7 @@ package com.tencent.devops.store.common.service import com.tencent.devops.common.pipeline.container.Stage -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo interface StoreDeptService { diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreManagementExtraService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreManagementExtraService.kt index 572fa794dbb1..f41d024d0b00 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreManagementExtraService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreManagementExtraService.kt @@ -28,6 +28,8 @@ package com.tencent.devops.store.common.service import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.repository.pojo.enums.TokenTypeEnum +import com.tencent.devops.store.pojo.common.enums.StoreMemberTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum interface StoreManagementExtraService { @@ -42,6 +44,37 @@ interface StoreManagementExtraService { */ fun deleteComponentRepoFile(userId: String, storeCode: String, storeType: StoreTypeEnum): Result + /** + * 删除组件代码库 + */ + fun deleteComponentCodeRepository( + userId: String, + repositoryId: String, + token: String, + tokenType: TokenTypeEnum + ): Result + + /** + * 添加组件代码库成员 + */ + fun addComponentRepositoryUser( + memberType: StoreMemberTypeEnum, + members: List, + repositoryId: String, + token: String, + tokenType: TokenTypeEnum + ): Result + + /** + * 删除组件代码库成员 + */ + fun deleteComponentRepositoryUser( + member: String, + repositoryId: String, + token: String, + tokenType: TokenTypeEnum + ): Result + /** * 检查卸载组件请求合法性 */ diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePipelineService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePipelineService.kt index 9d4ed6c26a18..092dbce286de 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePipelineService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePipelineService.kt @@ -29,6 +29,7 @@ package com.tencent.devops.store.common.service import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.store.pojo.common.UpdateStorePipelineModelRequest +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreRunPipelineParam interface StorePipelineService { @@ -50,4 +51,25 @@ interface StorePipelineService { fun runPipeline( storeRunPipelineParam: StoreRunPipelineParam ): Boolean + + // 创建组件内置流水线 + fun creatStorePipelineByStoreCode( + storeCode: String? = null, + storeType: String + ): String + + /** + * 删除组件内置流水线 + * @param userId 用户ID + * @param storeType 组件类型 + * @param storeCode 组件标识 + * @param excludeProjectCode 需排除的项目 + * @return 布尔值 + */ + fun deleteStoreInnerPipeline( + userId: String, + storeType: StoreTypeEnum? = null, + storeCode: String? = null, + excludeProjectCode: String? = null + ): Boolean } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePkgRunEnvInfoService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePkgRunEnvInfoService.kt index 6fa42ee889bd..1d252837381a 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePkgRunEnvInfoService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StorePkgRunEnvInfoService.kt @@ -70,6 +70,7 @@ interface StorePkgRunEnvInfoService { /** * 获取安装包运行时环境信息 + * @param devopsEnv 环境信息 * @param userId userId * @param storeType 组件类型 * @param language 开发语言 @@ -79,6 +80,7 @@ interface StorePkgRunEnvInfoService { * @return 安装包运行时环境信息 */ fun getStorePkgRunEnvInfo( + devopsEnv: String? = null, userId: String, storeType: StoreTypeEnum, language: String, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreProjectService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreProjectService.kt index 3c7730e37c93..28867d5da5ad 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreProjectService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreProjectService.kt @@ -33,6 +33,8 @@ import com.tencent.devops.store.pojo.common.InstallStoreReq import com.tencent.devops.store.pojo.common.InstalledProjRespItem import com.tencent.devops.store.pojo.common.StoreProjectInfo import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.test.StoreTestItem +import com.tencent.devops.store.pojo.common.test.StoreTestRequest /** * store项目通用业务逻辑类 @@ -108,4 +110,23 @@ interface StoreProjectService { * 更新组件初始化项目信息 */ fun updateStoreInitProject(userId: String, storeProjectInfo: StoreProjectInfo): Boolean + + /** + * 保存组件测试信息 + */ + fun saveStoreTestInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String, + storeTestRequest: StoreTestRequest + ): Boolean + + /** + * 获取组件测试信息 + */ + fun getStoreTestInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Set } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseService.kt index c7cb3b4aca48..5b33b0909548 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseService.kt @@ -27,6 +27,7 @@ package com.tencent.devops.store.common.service +import com.tencent.devops.store.pojo.common.StoreReleaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateResponse import com.tencent.devops.store.pojo.common.publication.StoreOfflineRequest @@ -83,6 +84,19 @@ interface StoreReleaseService { */ fun passTest(userId: String, storeId: String): Boolean + /** + * 填写信息 + * @param userId userId + * @param storeId 组件ID + * @param storeReleaseInfoUpdateRequest 组件发布信息修改请求报文 + * @return 布尔值 + */ + fun editReleaseInfo( + userId: String, + storeId: String, + storeReleaseInfoUpdateRequest: StoreReleaseInfoUpdateRequest + ): Boolean + /** * 处理发布 * @param userId userId @@ -117,4 +131,15 @@ interface StoreReleaseService { userId: String, storeId: String ): Boolean + + /** + * 返回上一步 + * @param userId userId + * @param storeId 组件ID + * @return 布尔值 + */ + fun back( + userId: String, + storeId: String + ): Boolean } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseSpecBusService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseSpecBusService.kt index 4d803491aea4..6f244a81be0b 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseSpecBusService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreReleaseSpecBusService.kt @@ -31,12 +31,29 @@ import com.tencent.devops.store.pojo.common.QueryComponentPkgEnvInfoParam import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.ReleaseProcessItem +import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest import com.tencent.devops.store.pojo.common.publication.StorePkgEnvInfo import com.tencent.devops.store.pojo.common.publication.StoreRunPipelineParam import com.tencent.devops.store.pojo.common.publication.StoreUpdateRequest interface StoreReleaseSpecBusService { + /** + * 执行新增组件请求前置业务 + * @param storeCreateRequest 新增组件请求报文 + */ + fun doStoreCreatePreBus( + storeCreateRequest: StoreCreateRequest + ) + + /** + * 执行更新组件请求前置业务 + * @param storeUpdateRequest 更新组件请求报文 + */ + fun doStoreUpdatePreBus( + storeUpdateRequest: StoreUpdateRequest + ) + /** * 对更新组件请求参数进行国际化转换个性化逻辑 * @param storeUpdateRequest 更新组件请求报文 @@ -123,4 +140,18 @@ interface StoreReleaseSpecBusService { isNormalUpgrade: Boolean, status: StoreStatusEnum ): List + + /** + * 执行组件环境信息业务 + * @param userId 流水线ID + * @param storeType 组件类型 + * @param storeCode 组件标识 + * @param version 组件版本 + */ + fun doStoreEnvBus( + storeCode: String, + storeType: StoreTypeEnum, + version: String, + userId: String + ) } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreVisibleDeptService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreVisibleDeptService.kt new file mode 100644 index 000000000000..5a26ee578445 --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/StoreVisibleDeptService.kt @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.service + +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.store.pojo.common.visible.UserStoreDeptInfoRequest +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.visible.DeptInfo +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptResp + +/** + * store组件可见范围逻辑类 + * since: 2019-01-08 + */ +@Suppress("ALL") +interface StoreVisibleDeptService { + + /** + * 查看store组件可见范围 + */ + fun getVisibleDept( + storeCode: String, + storeType: StoreTypeEnum, + deptStatusInfos: String? = null + ): Result + + /** + * 批量获取已经审核通过的可见范围 + */ + fun batchGetVisibleDept( + storeCodeList: List, + storeType: StoreTypeEnum + ): Result>> + + /** + * 设置store组件可见范围 + */ + fun addVisibleDept( + userId: String, + storeCode: String, + deptInfos: List, + storeType: StoreTypeEnum + ): Result + + /** + * 删除store组件可见范围 + */ + fun deleteVisibleDept( + userId: String, + storeCode: String, + deptIds: String, + storeType: StoreTypeEnum + ): Result + + /** + * 判断用户是否有组件的权限 + */ + fun checkUserInvalidVisibleStoreInfo( + userStoreDeptInfoRequest: UserStoreDeptInfoRequest + ): Boolean +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/ClassifyServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/ClassifyServiceImpl.kt index 27f259fa7d92..92859b0ddf80 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/ClassifyServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/ClassifyServiceImpl.kt @@ -162,8 +162,9 @@ class ClassifyServiceImpl @Autowired constructor( var flag = false if (null != classifyRecord) { val classifyType = classifyRecord.type - val classifyService = getStoreClassifyService(StoreTypeEnum.getStoreType(classifyType.toInt())) - flag = classifyService.getDeleteClassifyFlag(id) + val storeType = StoreTypeEnum.getStoreType(classifyType.toInt()) + val classifyService = getStoreClassifyService(storeType) + flag = classifyService.getDeleteClassifyFlag(id, StoreTypeEnum.valueOf(storeType)) } if (flag) { classifyDao.delete(dslContext, id) @@ -178,6 +179,12 @@ class ClassifyServiceImpl @Autowired constructor( } private fun getStoreClassifyService(storeType: String): AbstractClassifyService { - return SpringContextUtil.getBean(AbstractClassifyService::class.java, "${storeType}_CLASSIFY_SERVICE") + val beanName = "${storeType}_CLASSIFY_SERVICE" + return if (SpringContextUtil.isBeanExist(beanName)) { + SpringContextUtil.getBean(AbstractClassifyService::class.java, beanName) + } else { + // 获取默认的成员bean对象 + SpringContextUtil.getBean(AbstractClassifyService::class.java) + } } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/SensitiveApiServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/SensitiveApiServiceImpl.kt index 89f42388d0e8..375d4c3a0f2a 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/SensitiveApiServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/SensitiveApiServiceImpl.kt @@ -268,7 +268,7 @@ class SensitiveApiServiceImpl @Autowired constructor( val dbFileShaContent = storeBaseEnvExtQueryDao.getBaseExtEnvsByEnvId( dslContext = dslContext, envId = baseEnvRecord.id, - fieldName = "${KEY_FILE_SHA_CONTENT}_$signFileName" + "${KEY_FILE_SHA_CONTENT}_$signFileName" )?.getOrNull(0)?.fieldValue ?: baseEnvRecord.shaContent if (fileShaContent.lowercase() != dbFileShaContent) { throw ErrorCodeException( diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseCreateServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseCreateServiceImpl.kt index 980356a162e4..0b7b2f484777 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseCreateServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseCreateServiceImpl.kt @@ -32,6 +32,7 @@ import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE import com.tencent.devops.common.api.constant.CommonMessageCode import com.tencent.devops.common.api.constant.INIT_VERSION import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.store.common.dao.StoreBaseEnvExtManageDao import com.tencent.devops.store.common.dao.StoreBaseEnvManageDao import com.tencent.devops.store.common.dao.StoreBaseExtManageDao @@ -43,7 +44,9 @@ import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.dao.StoreProjectRelDao import com.tencent.devops.store.common.dao.StoreStatisticTotalDao import com.tencent.devops.store.common.service.StoreBaseCreateService +import com.tencent.devops.store.common.service.StoreReleaseSpecBusService import com.tencent.devops.store.common.utils.StoreReleaseUtils +import com.tencent.devops.store.common.utils.StoreUtils import com.tencent.devops.store.pojo.common.KEY_STORE_ID import com.tencent.devops.store.pojo.common.enums.StoreMemberTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreProjectTypeEnum @@ -97,6 +100,12 @@ class StoreBaseCreateServiceImpl @Autowired constructor( } } + override fun doStoreCreatePreBus(storeCreateRequest: StoreCreateRequest) { + val storeBaseCreateRequest = storeCreateRequest.baseInfo + val storeType = storeBaseCreateRequest.storeType + getStoreSpecBusService(storeType).doStoreCreatePreBus(storeCreateRequest) + } + override fun doStoreCreateDataPersistent(storeCreateRequest: StoreCreateRequest) { val storeBaseCreateRequest = storeCreateRequest.baseInfo val storeType = storeBaseCreateRequest.storeType @@ -157,6 +166,13 @@ class StoreBaseCreateServiceImpl @Autowired constructor( } } + private fun getStoreSpecBusService(storeType: StoreTypeEnum): StoreReleaseSpecBusService { + return SpringContextUtil.getBean( + StoreReleaseSpecBusService::class.java, + StoreUtils.getReleaseSpecBusServiceBeanName(storeType) + ) + } + private fun initStoreData( context: DSLContext, storeCode: String, @@ -179,22 +195,24 @@ class StoreBaseCreateServiceImpl @Autowired constructor( type = StoreMemberTypeEnum.ADMIN.type.toByte(), storeType = storeType.type.toByte() ) - // 添加组件与项目关联关系,type为0代表新增组件时关联的初始化项目 - storeProjectRelDao.addStoreProjectRel( - dslContext = context, - userId = userId, - storeCode = storeCode, - projectCode = storeCreateRequest.projectCode, - type = StoreProjectTypeEnum.INIT.type.toByte(), - storeType = storeType.type.toByte() - ) - storeProjectRelDao.addStoreProjectRel( - dslContext = context, - userId = userId, - storeCode = storeCode, - projectCode = storeCreateRequest.projectCode, - type = StoreProjectTypeEnum.TEST.type.toByte(), - storeType = storeType.type.toByte() - ) + storeCreateRequest.projectCode?.let { + // 添加组件与项目关联关系,type为0代表新增组件时关联的初始化项目 + storeProjectRelDao.addStoreProjectRel( + dslContext = context, + userId = userId, + storeCode = storeCode, + projectCode = it, + type = StoreProjectTypeEnum.INIT.type.toByte(), + storeType = storeType.type.toByte() + ) + storeProjectRelDao.addStoreProjectRel( + dslContext = context, + userId = userId, + storeCode = storeCode, + projectCode = it, + type = StoreProjectTypeEnum.TEST.type.toByte(), + storeType = storeType.type.toByte() + ) + } } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseDeleteServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseDeleteServiceImpl.kt index 8da5354a488e..096b51282125 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseDeleteServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseDeleteServiceImpl.kt @@ -29,14 +29,24 @@ package com.tencent.devops.store.common.service.impl import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID_DEFAULT_VALUE +import com.tencent.devops.common.api.constant.MESSAGE +import com.tencent.devops.common.api.constant.STATUS import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.client.Client import com.tencent.devops.common.service.utils.SpringContextUtil +import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.repository.api.ServiceOauthResource +import com.tencent.devops.repository.constant.RepositoryConstants.KEY_REPOSITORY_ID +import com.tencent.devops.repository.pojo.enums.TokenTypeEnum +import com.tencent.devops.store.common.dao.StoreBaseFeatureExtQueryDao import com.tencent.devops.store.common.dao.StoreBaseQueryDao import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.service.StoreBaseDeleteService import com.tencent.devops.store.common.service.StoreCommonService import com.tencent.devops.store.common.service.StoreManagementExtraService +import com.tencent.devops.store.constant.StoreConstants.KEY_FRAMEWORK_CODE import com.tencent.devops.store.constant.StoreMessageCode +import com.tencent.devops.store.pojo.common.enums.FrameworkCodeEnum import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreDeleteRequest @@ -44,20 +54,27 @@ import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service +import javax.ws.rs.NotFoundException @Service class StoreBaseDeleteServiceImpl @Autowired constructor( private val dslContext: DSLContext, private val storeMemberDao: StoreMemberDao, private val storeBaseQueryDao: StoreBaseQueryDao, - private val storeCommonService: StoreCommonService + private val storeBaseFeatureExtQueryDao: StoreBaseFeatureExtQueryDao, + private val storeCommonService: StoreCommonService, + private val client: Client ) : StoreBaseDeleteService { companion object { private val logger = LoggerFactory.getLogger(StoreComponentManageServiceImpl::class.java) } + @Value("\${git.devopsPrivateToken:}") + private val devopsPrivateToken: String = "" + private fun getStoreManagementExtraService(storeType: StoreTypeEnum): StoreManagementExtraService { return SpringContextUtil.getBean( StoreManagementExtraService::class.java, @@ -108,6 +125,58 @@ class StoreBaseDeleteServiceImpl @Autowired constructor( } } + override fun deleteComponentCodeRepository(handlerRequest: StoreDeleteRequest) { + val storeCode = handlerRequest.storeCode + val storeType = StoreTypeEnum.valueOf(handlerRequest.storeType) + val repositoryId = storeBaseFeatureExtQueryDao.getStoreBaseFeatureExt( + dslContext = dslContext, storeCode = storeCode, storeType = storeType, fieldName = KEY_REPOSITORY_ID + )?.fieldValue + if (!repositoryId.isNullOrBlank()) { + val bkStoreContext = handlerRequest.bkStoreContext + val userId = bkStoreContext[AUTH_HEADER_USER_ID]?.toString() ?: AUTH_HEADER_USER_ID_DEFAULT_VALUE + val frameworkCode = storeBaseFeatureExtQueryDao.getStoreBaseFeatureExt( + dslContext = dslContext, storeCode = storeCode, storeType = storeType, fieldName = KEY_FRAMEWORK_CODE + )?.fieldValue + val token: String + var tokenType = TokenTypeEnum.PRIVATE_KEY + if (frameworkCode == FrameworkCodeEnum.CUSTOM_FRAMEWORK.name) { + // 如果用户选择自定义框架方式发布,则使用用户自已的oauthToken去删除代码库 + val gitToken = client.get(ServiceOauthResource::class).gitGet(userId).data + ?: throw NotFoundException("cannot found access token for user($userId)") + token = gitToken.accessToken + tokenType = TokenTypeEnum.OAUTH + } else { + token = devopsPrivateToken + } + try { + val deleteRepositoryResult = getStoreManagementExtraService(storeType).deleteComponentCodeRepository( + userId = userId, + repositoryId = repositoryId, + token = token, + tokenType = tokenType + ) + if (deleteRepositoryResult.isNotOk()) { + setDeleteCodeRepositoryMsg(bkStoreContext, deleteRepositoryResult.message) + } + } catch (ignored: Throwable) { + // 组件删除代码库失败不终止删除流程,在接口返回报文给出提示信息 + logger.warn("deleteAtomRepository deleteComponentCodeRepository!", ignored) + setDeleteCodeRepositoryMsg(bkStoreContext, ignored.message) + } + } + } + + private fun setDeleteCodeRepositoryMsg( + bkStoreContext: MutableMap, + message: String? + ) { + bkStoreContext[STATUS] = StoreMessageCode.STORE_COMPONENT_CODE_REPOSITORY_DELETE_FAIL + bkStoreContext[MESSAGE] = I18nUtil.getCodeLanMessage( + messageCode = StoreMessageCode.STORE_COMPONENT_CODE_REPOSITORY_DELETE_FAIL, + params = arrayOf(message ?: "") + ) + } + override fun doStoreDeleteDataPersistent(handlerRequest: StoreDeleteRequest) { val storeCode = handlerRequest.storeCode val storeType = StoreTypeEnum.valueOf(handlerRequest.storeType) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseUpdateServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseUpdateServiceImpl.kt index 6392999b96ef..7ae02f021986 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseUpdateServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBaseUpdateServiceImpl.kt @@ -55,12 +55,12 @@ import com.tencent.devops.store.pojo.common.enums.ReleaseTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreBaseDataPO import com.tencent.devops.store.pojo.common.publication.StoreUpdateRequest -import java.time.LocalDateTime import org.apache.commons.codec.digest.DigestUtils import org.jooq.DSLContext import org.jooq.impl.DSL import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.time.LocalDateTime @Service @Suppress("LongParameterList") @@ -115,6 +115,12 @@ class StoreBaseUpdateServiceImpl @Autowired constructor( getStoreSpecBusService(storeType).doCheckStoreUpdateParamSpecBus(storeUpdateRequest) } + override fun doStoreUpdatePreBus(storeUpdateRequest: StoreUpdateRequest) { + val storeBaseUpdateRequest = storeUpdateRequest.baseInfo + val storeType = storeBaseUpdateRequest.storeType + getStoreSpecBusService(storeType).doStoreUpdatePreBus(storeUpdateRequest) + } + override fun doStoreUpdateDataPersistent(storeUpdateRequest: StoreUpdateRequest) { val storeBaseUpdateRequest = storeUpdateRequest.baseInfo val storeType = storeBaseUpdateRequest.storeType @@ -183,22 +189,18 @@ class StoreBaseUpdateServiceImpl @Autowired constructor( val context = DSL.using(t) storeBaseManageDao.saveStoreBaseData(context, storeBaseDataPO) if (!storeBaseExtDataPOs.isNullOrEmpty()) { - storeBaseExtManageDao.deleteStoreBaseExtInfo(context, storeId) storeBaseExtManageDao.batchSave(context, storeBaseExtDataPOs) } storeBaseFeatureDataPO?.let { storeBaseFeatureManageDao.saveStoreBaseFeatureData(context, it) } if (!storeBaseFeatureExtDataPOs.isNullOrEmpty()) { - storeBaseFeatureExtManageDao.deleteStoreBaseFeatureExtInfo(context, storeCode, storeType.type.toByte()) storeBaseFeatureExtManageDao.batchSave(context, storeBaseFeatureExtDataPOs) } if (!storeBaseEnvDataPOs.isNullOrEmpty()) { - storeBaseEnvManageDao.deleteStoreEnvInfo(context, storeId) storeBaseEnvManageDao.batchSave(context, storeBaseEnvDataPOs) } if (!storeBaseEnvExtDataPOs.isNullOrEmpty()) { - storeBaseEnvExtManageDao.deleteStoreEnvExtInfo(context, storeId) storeBaseEnvExtManageDao.batchSave(context, storeBaseEnvExtDataPOs) } storeLabelRelDao.deleteByStoreId(context, storeId) @@ -255,7 +257,7 @@ class StoreBaseUpdateServiceImpl @Autowired constructor( } if (flag) { throw ErrorCodeException( - errorCode = CommonMessageCode.PARAMETER_IS_INVALID, + errorCode = CommonMessageCode.PARAMETER_IS_EXIST, params = arrayOf(name) ) } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBuildServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBuildServiceImpl.kt index 9dcc4176a843..2248b9458831 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBuildServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreBuildServiceImpl.kt @@ -34,10 +34,10 @@ import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.store.common.dao.StorePipelineBuildRelDao import com.tencent.devops.store.common.dao.StorePipelineRelDao -import com.tencent.devops.store.pojo.common.publication.StoreBuildResultRequest -import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.common.service.AbstractStoreHandleBuildResultService import com.tencent.devops.store.common.service.StoreBuildService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.publication.StoreBuildResultRequest import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -60,15 +60,23 @@ class StoreBuildServiceImpl @Autowired constructor( ): Result { logger.info("handleStoreBuildResult params:[$pipelineId|$buildId|$storeBuildResultRequest]") // 查看该次构建流水线属于研发商店哪个组件类型 - val storePipelineRelRecord = storePipelineRelDao.getStorePipelineRelByPipelineId(dslContext, pipelineId) - ?: return I18nUtil.generateResponseDataObject( + val storeBuildInfoRecord = storePipelineBuildRelDao.getStorePipelineBuildRelByBuildId(dslContext, buildId) + logger.info("handleStoreBuildResult pipelineId:${storeBuildInfoRecord?.pipelineId}") + if (storeBuildInfoRecord == null) { + return I18nUtil.generateResponseDataObject( messageCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(pipelineId), language = I18nUtil.getLanguage(I18nUtil.getRequestUserId()) ) - val storeType = storePipelineRelRecord.storeType + } + val storeType = storeBuildInfoRecord.pipelineId?.let { + storePipelineRelDao.getStoreTypeByLatestPipelineId( + dslContext = dslContext, + pipelineId = it + ) + } val storeHandleBuildResultService = - getStoreHandleBuildResultService(StoreTypeEnum.getStoreType(storeType.toInt())) + getStoreHandleBuildResultService(StoreTypeEnum.getStoreType(storeType!!.toInt())) val result = storeHandleBuildResultService.handleStoreBuildResult(pipelineId, buildId, storeBuildResultRequest) logger.info("handleStoreBuildResult result is:$result") if (result.isNotOk() || result.data != true) { diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreCommonServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreCommonServiceImpl.kt index 0f713da61983..bbf89245b25d 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreCommonServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreCommonServiceImpl.kt @@ -301,9 +301,10 @@ abstract class StoreCommonServiceImpl : StoreCommonService { val storeBuildInfoRecord = storePipelineBuildRelDao.getStorePipelineBuildRel(dslContext, storeId) if (null != storeBuildInfoRecord) { val pipelineId = storeBuildInfoRecord.pipelineId - val storePipelineRelRecord = storePipelineRelDao.getStorePipelineRelByPipelineId( + val storePipelineRelRecord = storePipelineRelDao.getStorePipelineRelByStoreCode( dslContext = dslContext, - pipelineId = pipelineId + storeType = storeType, + storeCode = storeCode ) var projectCode = storePipelineRelRecord?.projectCode if (projectCode.isNullOrBlank()) { @@ -442,8 +443,13 @@ abstract class StoreCommonServiceImpl : StoreCommonService { val dbVersion = opBaseRecord.version val dbStatus = opBaseRecord.status // 判断首个版本对应的请求是否合法 + val validStatusList = listOf( + StoreStatusEnum.INIT.name, + StoreStatusEnum.COMMITTING.name, + StoreStatusEnum.GROUNDING_SUSPENSION.name + ) if (releaseType == ReleaseTypeEnum.NEW && dbVersion == INIT_VERSION && - dbStatus !in listOf(StoreStatusEnum.INIT.name, StoreStatusEnum.GROUNDING_SUSPENSION.name) + dbStatus !in validStatusList ) { throw ErrorCodeException(errorCode = StoreMessageCode.STORE_RELEASE_STEPS_ERROR) } @@ -532,4 +538,16 @@ abstract class StoreCommonServiceImpl : StoreCommonService { val currentNum = if (status == StoreStatusEnum.RELEASED) 1 else 0 return releaseTotalNum > currentNum } + + override fun getStoreCodeById( + storeId: String, + storeType: StoreTypeEnum + ): String { + var code = storeBaseQueryDao.getComponentById(dslContext, storeId)?.storeCode + if (code == null) { + val storeCommonDao = getStoreCommonDao(storeType.name) + code = storeCommonDao.getStoreCodeById(dslContext, storeId) + } + return code ?: "" + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentClassifyServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentClassifyServiceImpl.kt new file mode 100644 index 000000000000..4ae2684c2d3c --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentClassifyServiceImpl.kt @@ -0,0 +1,76 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ +package com.tencent.devops.store.common.service.impl + +import com.tencent.devops.store.common.dao.StoreBaseQueryDao +import com.tencent.devops.store.common.service.AbstractClassifyService +import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import org.jooq.DSLContext +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Primary +import org.springframework.stereotype.Service + +@Primary +@Service("DEFAULT_CLASSIFY_SERVICE") +class StoreComponentClassifyServiceImpl : AbstractClassifyService() { + + @Autowired + private lateinit var dslContext: DSLContext + + @Autowired + private lateinit var storeBaseQueryDao: StoreBaseQueryDao + + override fun getDeleteClassifyFlag(classifyId: String, storeType: StoreTypeEnum): Boolean { + // 允许删除分类是条件:1、该分类下的组件都不处于上架状态 2、该分类下的组件如果处于已下架状态但已经没人在用 + var flag = false + val releaseNum = storeBaseQueryDao.countByCondition( + dslContext = dslContext, + storeType = storeType, + status = StoreStatusEnum.RELEASED, + classifyId = classifyId + ) + if (releaseNum == 0) { + val undercarriagingNum = storeBaseQueryDao.countByCondition( + dslContext = dslContext, + storeType = storeType, + status = StoreStatusEnum.UNDERCARRIAGING, + classifyId = classifyId + ) + val undercarriagedNum = storeBaseQueryDao.countByCondition( + dslContext = dslContext, + storeType = storeType, + status = StoreStatusEnum.UNDERCARRIAGED, + classifyId = classifyId + ) + if (undercarriagingNum + undercarriagedNum == 0) { + flag = true + } + } + return flag + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentManageServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentManageServiceImpl.kt index 5b79edfeab09..0e22ece537fb 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentManageServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentManageServiceImpl.kt @@ -30,6 +30,8 @@ package com.tencent.devops.store.common.service.impl import com.tencent.devops.common.api.auth.AUTH_HEADER_USER_ID import com.tencent.devops.common.api.constant.CommonMessageCode import com.tencent.devops.common.api.constant.KEY_FILE_SHA_CONTENT +import com.tencent.devops.common.api.constant.MESSAGE +import com.tencent.devops.common.api.constant.STATUS import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.UUIDUtil @@ -51,6 +53,7 @@ import com.tencent.devops.store.common.dao.StoreBaseQueryDao import com.tencent.devops.store.common.dao.StoreLabelRelDao import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.handler.StoreDeleteCheckHandler +import com.tencent.devops.store.common.handler.StoreDeleteCodeRepositoryHandler import com.tencent.devops.store.common.handler.StoreDeleteDataPersistHandler import com.tencent.devops.store.common.handler.StoreDeleteHandlerChain import com.tencent.devops.store.common.handler.StoreDeleteRepoFileHandler @@ -63,12 +66,14 @@ import com.tencent.devops.store.common.utils.StoreUtils import com.tencent.devops.store.constant.StoreMessageCode import com.tencent.devops.store.pojo.common.InstallStoreReq import com.tencent.devops.store.pojo.common.InstalledPkgFileShaContentRequest +import com.tencent.devops.store.pojo.common.KEY_REPOSITORY_AUTHORIZER import com.tencent.devops.store.pojo.common.StoreBaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.UnInstallReq import com.tencent.devops.store.pojo.common.enums.ReasonTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreBaseEnvExtDataPO +import com.tencent.devops.store.pojo.common.publication.StoreBaseFeatureExtDataPO import com.tencent.devops.store.pojo.common.publication.StoreDeleteRequest import org.jooq.DSLContext import org.jooq.impl.DSL @@ -106,6 +111,9 @@ class StoreComponentManageServiceImpl : StoreComponentManageService { @Autowired lateinit var storeDeleteRepoFileHandler: StoreDeleteRepoFileHandler + @Autowired + lateinit var storeDeleteCodeRepositoryHandler: StoreDeleteCodeRepositoryHandler + @Autowired lateinit var storeDeleteDataPersistHandler: StoreDeleteDataPersistHandler @@ -358,12 +366,17 @@ class StoreComponentManageServiceImpl : StoreComponentManageService { val handlerList = mutableListOf( storeDeleteCheckHandler, storeDeleteRepoFileHandler, + storeDeleteCodeRepositoryHandler, storeDeleteDataPersistHandler ) val bkStoreContext = handlerRequest.bkStoreContext bkStoreContext[AUTH_HEADER_USER_ID] = userId StoreDeleteHandlerChain(handlerList).handleRequest(handlerRequest) - return Result(true) + return Result( + status = bkStoreContext[STATUS]?.toString()?.toInt() ?: 0, + data = true, + message = bkStoreContext[MESSAGE]?.toString() + ) } override fun validateComponentDownloadPermission( @@ -453,6 +466,43 @@ class StoreComponentManageServiceImpl : StoreComponentManageService { return Result(true) } + override fun updateStoreRepositoryAuthorizer( + userId: String, + storeType: StoreTypeEnum, + storeCode: String + ): Result { + // 判断用户是否是管理员,只有管理员才能重置授权 + if (!storeMemberDao.isStoreAdmin( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + ) + ) { + throw ErrorCodeException( + errorCode = StoreMessageCode.NO_COMPONENT_ADMIN_PERMISSION, + params = arrayOf(userId) + ) + } + val baseFeatureRecord = + storeBaseFeatureQueryDao.getBaseFeatureByCode(dslContext, storeCode, storeType) ?: throw ErrorCodeException( + errorCode = CommonMessageCode.PARAMETER_IS_INVALID, + params = arrayOf(storeCode) + ) + val storeBaseFeatureExtDataPO = StoreBaseFeatureExtDataPO( + id = UUIDUtil.generate(), + featureId = baseFeatureRecord.id, + storeCode = storeCode, + storeType = storeType, + fieldName = KEY_REPOSITORY_AUTHORIZER, + fieldValue = userId, + creator = userId, + modifier = userId + ) + storeBaseFeatureExtManageDao.batchSave(dslContext, listOf(storeBaseFeatureExtDataPO)) + return Result(true) + } + private fun getStoreBaseInstallService(storeType: StoreTypeEnum): StoreBaseInstallService { val beanName = when (storeType) { StoreTypeEnum.TEMPLATE -> { diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentMemberServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentMemberServiceImpl.kt index 5c31d4c74a9e..cc9dda3f0939 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentMemberServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentMemberServiceImpl.kt @@ -27,16 +27,28 @@ package com.tencent.devops.store.common.service.impl +import com.tencent.devops.common.api.constant.CommonMessageCode +import com.tencent.devops.common.api.exception.ErrorCodeException +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.service.utils.SpringContextUtil +import com.tencent.devops.repository.api.ServiceOauthResource +import com.tencent.devops.repository.constant.RepositoryConstants.KEY_REPOSITORY_ID +import com.tencent.devops.repository.pojo.enums.TokenTypeEnum +import com.tencent.devops.store.common.dao.StoreBaseFeatureExtQueryDao import com.tencent.devops.store.common.dao.StoreBaseQueryDao +import com.tencent.devops.store.common.service.StoreManagementExtraService import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.member.StoreMemberReq import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Primary import org.springframework.stereotype.Service +import javax.ws.rs.NotFoundException @Primary @Service class StoreComponentMemberServiceImpl @Autowired constructor( - private val storeBaseQueryDao: StoreBaseQueryDao + private val storeBaseQueryDao: StoreBaseQueryDao, + private val storeBaseFeatureExtQueryDao: StoreBaseFeatureExtQueryDao ) : StoreMemberServiceImpl() { override fun getStoreName(storeCode: String, storeType: StoreTypeEnum): String { @@ -46,4 +58,99 @@ class StoreComponentMemberServiceImpl @Autowired constructor( storeType = storeType )?.name ?: "" } + + override fun add( + userId: String, + storeMemberReq: StoreMemberReq, + storeType: StoreTypeEnum, + collaborationFlag: Boolean?, + sendNotify: Boolean, + checkPermissionFlag: Boolean, + testProjectCode: String? + ): Result { + val storeCode = storeMemberReq.storeCode + // 检查用户是否有权限操作 + super.checkUserPermission( + checkPermissionFlag = checkPermissionFlag, + userId = userId, + storeCode = storeCode, + storeType = storeType + ) + val repositoryId = storeBaseFeatureExtQueryDao.getStoreBaseFeatureExt( + dslContext = dslContext, storeCode = storeCode, storeType = storeType, fieldName = KEY_REPOSITORY_ID + )?.fieldValue + if (!repositoryId.isNullOrBlank()) { + val gitToken = client.get(ServiceOauthResource::class).gitGet(userId).data + ?: throw NotFoundException("cannot found access token for user($userId)") + getStoreManagementExtraService(storeType).addComponentRepositoryUser( + memberType = storeMemberReq.type, + members = storeMemberReq.member, + repositoryId = repositoryId, + token = gitToken.accessToken, + tokenType = TokenTypeEnum.OAUTH + ) + } + return super.add( + userId = userId, + storeMemberReq = storeMemberReq, + storeType = storeType, + collaborationFlag = collaborationFlag, + sendNotify = sendNotify, + checkPermissionFlag = false, + testProjectCode = testProjectCode + ) + } + + override fun delete( + userId: String, + id: String, + storeCode: String, + storeType: StoreTypeEnum, + checkPermissionFlag: Boolean + ): Result { + // 检查用户是否有权限操作 + super.checkUserPermission( + checkPermissionFlag = checkPermissionFlag, + userId = userId, + storeCode = storeCode, + storeType = storeType + ) + val memberRecord = storeMemberDao.getById(dslContext, id) ?: throw ErrorCodeException( + errorCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(id) + ) + // 如果删除的是管理员,只剩一个管理员则不允许删除 + if ((memberRecord.type).toInt() == 0) { + val validateAdminResult = isStoreHasAdmins(storeCode, storeType) + if (validateAdminResult.isNotOk()) { + return Result(status = validateAdminResult.status, message = validateAdminResult.message, data = false) + } + } + val repositoryId = storeBaseFeatureExtQueryDao.getStoreBaseFeatureExt( + dslContext = dslContext, storeCode = storeCode, storeType = storeType, fieldName = KEY_REPOSITORY_ID + )?.fieldValue + if (!repositoryId.isNullOrBlank()) { + val gitToken = client.get(ServiceOauthResource::class).gitGet(userId).data + ?: throw NotFoundException("cannot found access token for user($userId)") + getStoreManagementExtraService(storeType).deleteComponentRepositoryUser( + member = memberRecord.username, + repositoryId = repositoryId, + token = gitToken.accessToken, + tokenType = TokenTypeEnum.OAUTH + ) + } + return super.delete( + userId = userId, + id = id, + storeCode = storeCode, + storeType = storeType, + checkPermissionFlag = false + ) + } + + private fun getStoreManagementExtraService(storeType: StoreTypeEnum): StoreManagementExtraService { + return SpringContextUtil.getBean( + StoreManagementExtraService::class.java, + "${storeType}_MANAGEMENT_EXTRA_SERVICE" + ) + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentQueryServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentQueryServiceImpl.kt index 092d3aa6e55f..d6ffe41489a4 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentQueryServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreComponentQueryServiceImpl.kt @@ -508,7 +508,13 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { val userCommentInfo = storeCommentService.getStoreUserCommentInfo(userId, storeCode, storeType) val baseExtRecords = storeBaseExtQueryDao.getBaseExtByIds(dslContext, listOf(storeId)) - val extData = baseExtRecords.associateBy({ it.fieldName }, { formatJson(it.fieldValue) }) + var extData = baseExtRecords.associateBy({ it.fieldName }, { formatJson(it.fieldValue) }) + val baseFeatureExtRecords = storeBaseFeatureExtQueryDao.queryStoreBaseFeatureExt( + dslContext = dslContext, + storeCode = storeCode, + storeType = storeType + ) + extData = extData.plus(baseFeatureExtRecords.associateBy({ it.fieldName }, { formatJson(it.fieldValue) })) val htmlTemplateVersion = extData[KEY_HTML_TEMPLATE_VERSION] val initProjectCode = if (htmlTemplateVersion != null && htmlTemplateVersion == FrontendTypeEnum.HISTORY.typeVersion) { @@ -566,6 +572,7 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { recommendFlag = storeFeatureRecord.recommendFlag, certificationFlag = storeFeatureRecord.certificationFlag, type = storeFeatureRecord.type, + rdType = storeFeatureRecord.rdType, userCommentInfo = userCommentInfo, editFlag = StoreUtils.checkEditCondition(newestComponentRecord!!.status), honorInfos = storeHonorService.getStoreHonor(userId, storeType, storeCode), @@ -749,7 +756,7 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { } private fun getStoreInfos(storeInfoQuery: StoreInfoQuery): Pair> { - if (storeInfoQuery.queryProjectComponentFlag) { + if (storeInfoQuery.getSpecQueryFlag()) { handleQueryStoreCodes(storeInfoQuery) } val count = marketStoreQueryDao.count( @@ -769,6 +776,7 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { ) { val projectCode = storeInfoQuery.projectCode!! val storeType = StoreTypeEnum.valueOf(storeInfoQuery.storeType) + val queryTestFlag = storeInfoQuery.queryTestFlag // 查询项目下已安装的组件版本信息 val installComponentMap = storeProjectService.getProjectComponents( projectCode = projectCode, @@ -778,52 +786,71 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { ), instanceId = storeInfoQuery.instanceId ) ?: emptyMap() - // 查询项目下可调试的组件版本信息 - val testComponentMap = storeProjectService.getProjectComponents( - projectCode = projectCode, - storeType = storeType.type.toByte(), - storeProjectTypes = listOf( - StoreProjectTypeEnum.TEST.type.toByte() - ), - instanceId = storeInfoQuery.instanceId - ) ?: emptyMap() - val publicComponentList = storeBaseFeatureQueryDao.getAllPublicComponent(dslContext, storeType) val tStoreBase = TStoreBase.T_STORE_BASE - // 查询测试或者审核中组件最新版本信息 - val testComponentVersionMap = storeBaseQueryDao.getValidComponentsByCodes( - dslContext = dslContext, - storeCodes = testComponentMap.keys.plus(publicComponentList), - storeType = storeType, - testComponentFlag = true - ).intoMap({ it[tStoreBase.STORE_CODE] }, { it[tStoreBase.VERSION] }) - val testStoreCodes = testComponentVersionMap.keys - // 查询非测试或者审核中组件最新发布版本信息 - val normalStoreCodes = installComponentMap.keys.plus(publicComponentList).toMutableSet() - normalStoreCodes.removeAll(testStoreCodes) - val componentVersionMap = storeBaseQueryDao.getValidComponentsByCodes( - dslContext = dslContext, - storeCodes = normalStoreCodes, - storeType = storeType, - testComponentFlag = false - ).intoMap({ it[tStoreBase.STORE_CODE] }, { it[tStoreBase.VERSION] }).toMutableMap() - componentVersionMap.putAll(testComponentVersionMap) - // 比较当前安装的版本与组件最新版本 + var testStoreCodes = emptySet() + var testComponentVersionMap: Map? = null + if (queryTestFlag != false) { + // 查询项目下可调试的组件版本信息 + val testComponentMap = storeProjectService.getProjectComponents( + projectCode = projectCode, + storeType = storeType.type.toByte(), + storeProjectTypes = listOf( + StoreProjectTypeEnum.TEST.type.toByte() + ), + instanceId = storeInfoQuery.instanceId + ) ?: emptyMap() + // 查询测试或者审核中组件最新版本信息 + testComponentVersionMap = storeBaseQueryDao.getValidComponentsByCodes( + dslContext = dslContext, + storeCodes = testComponentMap.keys, + storeType = storeType, + testComponentFlag = true + ).intoMap({ it[tStoreBase.STORE_CODE] }, { it[tStoreBase.VERSION] }) + testStoreCodes = testComponentVersionMap.keys + } + val componentVersionMap = if (queryTestFlag != true) { + // 查询非测试或者审核中组件最新发布版本信息 + val publicComponentList = storeBaseFeatureQueryDao.getAllPublicComponent(dslContext, storeType) + val normalStoreCodes = installComponentMap.keys.plus(publicComponentList).toMutableSet() + normalStoreCodes.removeAll(testStoreCodes) + val normalComponentVersionMap = storeBaseQueryDao.getValidComponentsByCodes( + dslContext = dslContext, + storeCodes = normalStoreCodes, + storeType = storeType, + testComponentFlag = false + ).intoMap({ it[tStoreBase.STORE_CODE] }, { it[tStoreBase.VERSION] }).toMutableMap() + testComponentVersionMap?.let { + normalComponentVersionMap += it + } + normalComponentVersionMap + } else { + testComponentVersionMap + } + val finalNormalStoreCodes = mutableSetOf() val finalTestStoreCodes = mutableSetOf() - componentVersionMap.forEach { + componentVersionMap?.forEach { val storeCode = it.key val version = it.value val updateFlag = storeInfoQuery.updateFlag - val installedVersion = installComponentMap[storeCode] - val shouldAddStoreCode = when { - updateFlag == null -> true - updateFlag -> installedVersion == null || StoreUtils.isGreaterVersion(version, installedVersion) - else -> installedVersion != null && !StoreUtils.isGreaterVersion(version, installedVersion) + val installed = storeInfoQuery.installed + installed?.let { + // 根据是否安装条件筛选组件 + if (installed != installComponentMap.containsKey(storeCode)) { + return@forEach + } } - if (shouldAddStoreCode) { - if (testStoreCodes.contains(storeCode)) { - finalTestStoreCodes.add(storeCode) - } else { + val installedVersion = installComponentMap[storeCode] + if (testStoreCodes.contains(storeCode) && updateFlag != false) { + finalTestStoreCodes.add(storeCode) + } else { + // 比较当前安装的版本与组件最新版本 + val shouldAddStoreCode = when { + updateFlag == null -> true + updateFlag -> installedVersion == null || StoreUtils.isGreaterVersion(version, installedVersion) + else -> installedVersion != null && !StoreUtils.isGreaterVersion(version, installedVersion) + } + if (shouldAddStoreCode) { finalNormalStoreCodes.add(storeCode) } } @@ -933,11 +960,19 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { val storeCode = record[tStoreBase.STORE_CODE] val statistic = storeStatisticData[storeCode] val version = record[tStoreBase.VERSION] + val status = record[tStoreBase.STATUS] // 组件是否已安装 - val installed = projectCode?.let { installedInfoMap?.contains(storeCode) } + val installed = storeInfoQuery.installed ?: run { + projectCode?.let { installedInfoMap?.contains(storeCode) } + } // 是否可更新 val installedVersion = installedInfoMap?.get(storeCode) - val updateFlag = installedVersion == null || StoreUtils.isGreaterVersion(version, installedVersion) + val updateFlag = storeInfoQuery.updateFlag ?: run { + installedVersion == null || StoreUtils.isGreaterVersion( + version, + installedVersion + ) || status in StoreStatusEnum.getTestStatusList() + } val osList = queryComponentOsName(storeCode, storeTypeEnum) val baseExtResult = storeBaseExtQueryDao.getBaseExtByEnvId( dslContext = dslContext, @@ -957,6 +992,7 @@ class StoreComponentQueryServiceImpl : StoreComponentQueryService { name = record[tStoreBase.NAME], code = storeCode, version = version, + status = status, type = StoreTypeEnum.getStoreType(record[tStoreBase.STORE_TYPE].toInt()), rdType = record[tStoreBaseFeature.RD_TYPE], classifyCode = classifyMap[record[tStoreBase.CLASSIFY_ID] as String], diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDailyStatisticServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDailyStatisticServiceImpl.kt index bfba6f31a0e3..970e08673f48 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDailyStatisticServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDailyStatisticServiceImpl.kt @@ -35,6 +35,7 @@ import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.statistic.StoreDailyStatistic import com.tencent.devops.store.pojo.common.statistic.StoreDailyStatisticRequest import org.jooq.DSLContext +import org.jooq.impl.DSL import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import java.time.LocalDateTime @@ -102,20 +103,23 @@ class StoreDailyStatisticServiceImpl @Autowired constructor( storeType = storeType.type.toByte(), statisticsTime = storeDailyStatisticRequest.statisticsTime ) - if (storeDailyStatistic != null) { - storeStatisticDailyDao.updateDailyStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType.type.toByte(), - storeDailyStatisticRequest = storeDailyStatisticRequest - ) - } else { - storeStatisticDailyDao.insertDailyStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType.type.toByte(), - storeDailyStatisticRequest = storeDailyStatisticRequest - ) + dslContext.transaction { t -> + val context = DSL.using(t) + if (storeDailyStatistic != null) { + storeStatisticDailyDao.updateDailyStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType.type.toByte(), + storeDailyStatisticRequest = storeDailyStatisticRequest + ) + } else { + storeStatisticDailyDao.insertDailyStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType.type.toByte(), + storeDailyStatisticRequest = storeDailyStatisticRequest + ) + } } return true } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDeptServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDeptServiceImpl.kt index 2be2fb0672ff..5c549ea35e3b 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDeptServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreDeptServiceImpl.kt @@ -32,7 +32,7 @@ import com.tencent.devops.common.pipeline.container.Stage import com.tencent.devops.common.pipeline.container.VMBuildContainer import com.tencent.devops.common.pipeline.type.StoreDispatchType import com.tencent.devops.store.common.dao.StoreDeptRelDao -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.common.service.StoreDeptService import org.jooq.DSLContext diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreLogServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreLogServiceImpl.kt index bf619636973a..dc2cd5def5c2 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreLogServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreLogServiceImpl.kt @@ -34,7 +34,8 @@ import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.log.api.ServiceLogResource import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.common.dao.StoreMemberDao -import com.tencent.devops.store.common.dao.StorePipelineRelDao +import com.tencent.devops.store.common.dao.StorePipelineBuildRelDao +import com.tencent.devops.store.common.service.StoreCommonService import com.tencent.devops.store.common.service.StoreLogService import com.tencent.devops.store.constant.StoreMessageCode.GET_INFO_NO_PERMISSION import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum @@ -51,9 +52,10 @@ import org.springframework.stereotype.Service class StoreLogServiceImpl @Autowired constructor( private val client: Client, private val dslContext: DSLContext, - private val storePipelineRelDao: StorePipelineRelDao, + private val storePipelineBuildRelDao: StorePipelineBuildRelDao, private val storeMemberDao: StoreMemberDao, - private val storeInnerPipelineConfig: StoreInnerPipelineConfig + private val storeInnerPipelineConfig: StoreInnerPipelineConfig, + private val storeCommonService: StoreCommonService ) : StoreLogService { override fun getInitLogs( @@ -66,7 +68,7 @@ class StoreLogServiceImpl @Autowired constructor( tag: String?, executeCount: Int? ): Result { - val validateResult = validateUserQueryPermission(storeType, pipelineId, userId) + val validateResult = validateUserQueryPermission(storeType, buildId, userId) if (validateResult.isNotOk()) { return Result(status = validateResult.status, message = validateResult.message, data = null) } @@ -101,7 +103,7 @@ class StoreLogServiceImpl @Autowired constructor( tag: String?, executeCount: Int? ): Result { - val validateResult = validateUserQueryPermission(storeType, pipelineId, userId) + val validateResult = validateUserQueryPermission(storeType, buildId, userId) if (validateResult.isNotOk()) { return Result(status = validateResult.status, message = validateResult.message, data = null) } @@ -140,7 +142,7 @@ class StoreLogServiceImpl @Autowired constructor( tag: String?, executeCount: Int? ): Result { - val validateResult = validateUserQueryPermission(storeType, pipelineId, userId) + val validateResult = validateUserQueryPermission(storeType, buildId, userId) if (validateResult.isNotOk()) { return Result(status = validateResult.status, message = validateResult.message, data = null) } @@ -170,27 +172,28 @@ class StoreLogServiceImpl @Autowired constructor( private fun validateUserQueryPermission( storeType: StoreTypeEnum, - pipelineId: String, + buildId: String, userId: String ): Result { // 查询是否是插件的成员,只有插件的成员才能看日志 - val storePipelineRelRecord = storePipelineRelDao.getStorePipelineRelByPipelineId(dslContext, pipelineId) + val storeBuildInfoRecord = storePipelineBuildRelDao.getStorePipelineBuildRelByBuildId(dslContext, buildId) ?: return I18nUtil.generateResponseDataObject( messageCode = CommonMessageCode.PARAMETER_IS_INVALID, - params = arrayOf(pipelineId), + params = arrayOf(buildId), language = I18nUtil.getLanguage(userId) ) + val storeCode = storeCommonService.getStoreCodeById(storeBuildInfoRecord.storeId, storeType) val flag = storeMemberDao.isStoreMember( dslContext = dslContext, userId = userId, - storeCode = storePipelineRelRecord.storeCode, + storeCode = storeCode, storeType = storeType.type.toByte() ) if (!flag) { return I18nUtil.generateResponseDataObject( messageCode = GET_INFO_NO_PERMISSION, language = I18nUtil.getLanguage(userId), - params = arrayOf(storePipelineRelRecord.storeCode) + params = arrayOf(storeCode) ) } return Result(true) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreMemberServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreMemberServiceImpl.kt index 179f3d631c85..24230d2b6b91 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreMemberServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreMemberServiceImpl.kt @@ -36,6 +36,7 @@ import com.tencent.devops.common.client.Client import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.model.store.tables.records.TStoreMemberRecord import com.tencent.devops.project.api.service.ServiceProjectResource +import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.constant.StoreMessageCode import com.tencent.devops.store.constant.StoreMessageCode.GET_INFO_NO_PERMISSION import com.tencent.devops.store.constant.StoreMessageCode.NO_COMPONENT_ADMIN_PERMISSION @@ -69,6 +70,8 @@ abstract class StoreMemberServiceImpl : StoreMemberService { lateinit var storeProjectRelDao: StoreProjectRelDao @Autowired lateinit var storeNotifyService: StoreNotifyService + @Autowired + lateinit var storeInnerPipelineConfig: StoreInnerPipelineConfig private val executorService = Executors.newFixedThreadPool(5) @@ -196,18 +199,12 @@ abstract class StoreMemberServiceImpl : StoreMemberService { ): Result { val storeCode = storeMemberReq.storeCode val type = storeMemberReq.type.type.toByte() - if (checkPermissionFlag && !storeMemberDao.isStoreAdmin( - dslContext = dslContext, - userId = userId, - storeCode = storeCode, - storeType = storeType.type.toByte() - )) { - return I18nUtil.generateResponseDataObject( - messageCode = NO_COMPONENT_ADMIN_PERMISSION, - language = I18nUtil.getLanguage(userId), - params = arrayOf(storeCode) - ) - } + checkUserPermission( + checkPermissionFlag = checkPermissionFlag, + userId = userId, + storeCode = storeCode, + storeType = storeType + ) val receivers = mutableSetOf() for (item in storeMemberReq.member) { if (storeMemberDao.isStoreMember(dslContext, item, storeCode, storeType.type.toByte())) { @@ -225,7 +222,7 @@ abstract class StoreMemberServiceImpl : StoreMemberService { projectCode = testProjectCode, storeProjectType = StoreProjectTypeEnum.TEST ) - } else if (null != collaborationFlag && !collaborationFlag) { + } else if (collaborationFlag != true && storeType != StoreTypeEnum.DEVX) { // 协作申请方式,添加成员时无需再添加调试项目 storeProjectRelDao.addStoreProjectRel( dslContext = context, @@ -258,6 +255,23 @@ abstract class StoreMemberServiceImpl : StoreMemberService { return Result(true) } + protected fun checkUserPermission( + checkPermissionFlag: Boolean, + userId: String, + storeCode: String, + storeType: StoreTypeEnum + ) { + if (checkPermissionFlag && !storeMemberDao.isStoreAdmin( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + ) + ) { + throw ErrorCodeException(errorCode = NO_COMPONENT_ADMIN_PERMISSION, params = arrayOf(storeCode)) + } + } + /** * 删除store组件成员 */ @@ -269,19 +283,12 @@ abstract class StoreMemberServiceImpl : StoreMemberService { checkPermissionFlag: Boolean ): Result { logger.info("deleteMember params:[$userId|$id|$storeCode|$storeType|$checkPermissionFlag") - if (checkPermissionFlag && !storeMemberDao.isStoreAdmin( - dslContext = dslContext, - userId = userId, - storeCode = storeCode, - storeType = storeType.type.toByte() - ) - ) { - return I18nUtil.generateResponseDataObject( - messageCode = NO_COMPONENT_ADMIN_PERMISSION, - language = I18nUtil.getLanguage(userId), - params = arrayOf(storeCode) - ) - } + checkUserPermission( + checkPermissionFlag = checkPermissionFlag, + userId = userId, + storeCode = storeCode, + storeType = storeType + ) val record = storeMemberDao.getById(dslContext, id) if (record != null) { if ((record.type).toInt() == 0) { @@ -294,7 +301,7 @@ abstract class StoreMemberServiceImpl : StoreMemberService { val context = DSL.using(t) storeMemberDao.delete(context, id) // 删除成员对应的调试项目 - storeProjectRelDao.deleteUserStoreTestProject( + storeProjectRelDao.deleteStoreProject( dslContext = context, userId = record.username, storeProjectType = StoreProjectTypeEnum.TEST, @@ -392,10 +399,7 @@ abstract class StoreMemberServiceImpl : StoreMemberService { override fun isStoreHasAdmins(storeCode: String, storeType: StoreTypeEnum): Result { val adminCount = storeMemberDao.countAdmin(dslContext, storeCode, storeType.type.toByte()) if (adminCount <= 1) { - return I18nUtil.generateResponseDataObject( - messageCode = StoreMessageCode.USER_COMPONENT_ADMIN_COUNT_ERROR, - language = I18nUtil.getLanguage(I18nUtil.getRequestUserId()) - ) + throw ErrorCodeException(errorCode = StoreMessageCode.USER_COMPONENT_ADMIN_COUNT_ERROR) } return Result(true) } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePipelineServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePipelineServiceImpl.kt index 2c1c4ae8ab69..3014c4251e49 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePipelineServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePipelineServiceImpl.kt @@ -35,20 +35,22 @@ import com.tencent.devops.common.api.constant.KEY_SCRIPT import com.tencent.devops.common.api.constant.KEY_VERSION import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.api.util.PageUtil import com.tencent.devops.common.api.util.UUIDUtil import com.tencent.devops.common.client.Client +import com.tencent.devops.common.pipeline.Model import com.tencent.devops.common.pipeline.enums.BuildStatus import com.tencent.devops.common.pipeline.enums.ChannelCode import com.tencent.devops.common.pipeline.enums.StartType -import com.tencent.devops.common.pipeline.pojo.StoreInitPipelineReq +import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.service.gray.Gray import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.model.store.tables.TStoreProjectRel import com.tencent.devops.process.api.service.ServiceBuildResource -import com.tencent.devops.process.api.service.ServicePipelineInitResource +import com.tencent.devops.process.api.service.ServicePipelineResource import com.tencent.devops.process.api.service.ServicePipelineSettingResource import com.tencent.devops.process.pojo.setting.PipelineModelVersion import com.tencent.devops.process.pojo.setting.UpdatePipelineModelRequest @@ -74,17 +76,18 @@ import com.tencent.devops.store.pojo.common.KEY_PROJECT_CODE import com.tencent.devops.store.pojo.common.KEY_STORE_CODE import com.tencent.devops.store.pojo.common.OperationLogCreateRequest import com.tencent.devops.store.pojo.common.UpdateStorePipelineModelRequest +import com.tencent.devops.store.pojo.common.config.BusinessConfigRequest import com.tencent.devops.store.pojo.common.enums.ScopeTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreOperationTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.publication.StoreRunPipelineParam import com.tencent.devops.store.pojo.common.publication.UpdateStoreBaseDataPO +import java.util.concurrent.Executors import org.apache.commons.lang3.StringEscapeUtils import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import java.util.concurrent.Executors @Suppress("ALL") @Service @@ -118,13 +121,11 @@ class StorePipelineServiceImpl @Autowired constructor( userId: String, updateStorePipelineModelRequest: UpdateStorePipelineModelRequest ): Result { - val taskId = UUIDUtil.generate() val scopeType = updateStorePipelineModelRequest.scopeType val storeType = updateStorePipelineModelRequest.storeType val storeCodeList = updateStorePipelineModelRequest.storeCodeList val updatePipelineModel = updateStorePipelineModelRequest.pipelineModel val pipelineModel: String - val grayPipelineModel: String if (updatePipelineModel.isNullOrBlank()) { val pipelineModelConfig = businessConfigDao.get(dslContext, storeType, featureName, "PIPELINE_MODEL") @@ -132,51 +133,18 @@ class StorePipelineServiceImpl @Autowired constructor( messageCode = CommonMessageCode.SYSTEM_ERROR, language = I18nUtil.getLanguage(userId) ) - val grayPipelineModelConfig = - businessConfigDao.get(dslContext, storeType, featureName, "GRAY_PIPELINE_MODEL") - ?: return I18nUtil.generateResponseDataObject( - messageCode = CommonMessageCode.SYSTEM_ERROR, - language = I18nUtil.getLanguage(userId) - ) pipelineModel = pipelineModelConfig.configValue - grayPipelineModel = grayPipelineModelConfig.configValue } else { pipelineModel = updatePipelineModel - grayPipelineModel = updatePipelineModel } + val innerPipelineUser = storeInnerPipelineConfig.innerPipelineUser when (scopeType) { ScopeTypeEnum.ALL.name -> { - handleStorePipelineModel( + handleStorePublicPipelineModel( storeType = storeType, - taskId = taskId, - userId = userId, - pipelineModel = grayPipelineModel, - grayFlag = true - ) - handleStorePipelineModel( - storeType = storeType, - taskId = taskId, - userId = userId, - pipelineModel = pipelineModel, - grayFlag = false - ) - } - ScopeTypeEnum.GRAY.name -> { - handleStorePipelineModel( - storeType = storeType, - taskId = taskId, - userId = userId, - pipelineModel = grayPipelineModel, - grayFlag = true - ) - } - ScopeTypeEnum.NO_GRAY.name -> { - handleStorePipelineModel( - storeType = storeType, - taskId = taskId, - userId = userId, - pipelineModel = pipelineModel, - grayFlag = false + storeCode = null, + userId = innerPipelineUser, + pipelineModel = pipelineModel ) } ScopeTypeEnum.SPEC.name -> { @@ -187,15 +155,14 @@ class StorePipelineServiceImpl @Autowired constructor( language = I18nUtil.getLanguage(userId) ) } - updatePipelineModel( - storeType = storeType, - storeCodeList = storeCodeList, - userId = userId, - taskId = taskId, - defaultPipelineModel = pipelineModel, - checkGrayFlag = true, - grayPipelineModel = grayPipelineModel - ) + storeCodeList.forEach { + handleStorePublicPipelineModel( + storeType = storeType, + storeCode = it, + userId = storeInnerPipelineConfig.innerPipelineUser, + pipelineModel = pipelineModel + ) + } } } return Result(true) @@ -214,62 +181,20 @@ class StorePipelineServiceImpl @Autowired constructor( ) val innerPipelineProject = storeInnerPipelineConfig.innerPipelineProject val innerPipelineUser = storeInnerPipelineConfig.innerPipelineUser + val pipelineId: String // 生成流水线启动参数 val startParams = storeReleaseSpecBusService.getStoreRunPipelineStartParams(storeRunPipelineParam) if (null == storePipelineRelRecord) { - val pipelineModelConfig = businessConfigDao.get( + pipelineId = creatStorePipelineByStoreCode(storeType = storeType.name) + storePipelineRelDao.add( dslContext = dslContext, - business = storeType.name, - feature = "initBuildPipeline", - businessValue = "PIPELINE_MODEL" - ) - var pipelineModel = pipelineModelConfig!!.configValue - val pipelineName = "am-$storeType-$storeCode-${UUIDUtil.generate()}" - val paramMap = mapOf( - KEY_PIPELINE_NAME to pipelineName - ) - // 将流水线模型中的变量替换成具体的值 - paramMap.forEach { (key, value) -> - pipelineModel = pipelineModel.replace("#{$key}", value) - } - val storeInitPipelineReq = StoreInitPipelineReq( - pipelineModel = pipelineModel, - startParams = startParams + storeCode = storeCode, + storeType = storeType, + pipelineId = pipelineId, + projectCode = innerPipelineProject ) - val storeInitPipelineResp = client.get(ServicePipelineInitResource::class) - .initStorePipeline(innerPipelineUser, innerPipelineProject, storeInitPipelineReq).data - logger.info("runStorePipeline storeInitPipelineResp is:$storeInitPipelineResp") - if (null != storeInitPipelineResp) { - val pipelineId = storeInitPipelineResp.pipelineId - storePipelineRelDao.add( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType, - pipelineId = pipelineId, - projectCode = innerPipelineProject - ) - val buildId = storeInitPipelineResp.buildId - if (!buildId.isNullOrBlank()) { - storePipelineBuildRelDao.add( - dslContext = dslContext, - storeId = storeId, - pipelineId = pipelineId, - buildId = buildId - ) - } - val storeStatus = storeReleaseSpecBusService.getStoreRunPipelineStatus(buildId) - storeStatus?.let { - storeBaseManageDao.updateStoreBaseInfo( - dslContext = dslContext, - updateStoreBaseDataPO = UpdateStoreBaseDataPO( - id = storeId, - status = storeStatus, - modifier = userId - ) - ) - } - } } else { + pipelineId = storePipelineRelRecord.pipelineId val buildInfoRecord = storePipelineBuildRelDao.getStorePipelineBuildRel(dslContext, storeId) // 判断插件版本最近一次的构建是否完成 val buildResult = if (buildInfoRecord != null) { @@ -293,37 +218,88 @@ class StorePipelineServiceImpl @Autowired constructor( ) } } - // 触发执行流水线 - val pipelineId = storePipelineRelRecord.pipelineId - val buildIdObj = client.get(ServiceBuildResource::class).manualStartupNew( - userId = innerPipelineUser, - projectId = innerPipelineProject, + } + // 触发执行流水线 + val buildIdObj = client.get(ServiceBuildResource::class).manualStartupNew( + userId = innerPipelineUser, + projectId = innerPipelineProject, + pipelineId = pipelineId, + values = startParams, + channelCode = ChannelCode.AM, + startType = StartType.SERVICE + ).data + var buildId: String? = null + if (null != buildIdObj) { + buildId = buildIdObj.id + storePipelineBuildRelDao.add( + dslContext = dslContext, + storeId = storeId, pipelineId = pipelineId, - values = startParams, - channelCode = ChannelCode.AM, - startType = StartType.SERVICE - ).data - var buildId: String? = null - if (null != buildIdObj) { - buildId = buildIdObj.id - storePipelineBuildRelDao.add( - dslContext = dslContext, - storeId = storeId, - pipelineId = pipelineId, - buildId = buildId + buildId = buildId + ) + } + val storeStatus = storeReleaseSpecBusService.getStoreRunPipelineStatus(buildId) + storeStatus?.let { + storeBaseManageDao.updateStoreBaseInfo( + dslContext = dslContext, + updateStoreBaseDataPO = UpdateStoreBaseDataPO( + id = storeId, + status = storeStatus, + modifier = userId ) - } - val storeStatus = storeReleaseSpecBusService.getStoreRunPipelineStatus(buildId) - storeStatus?.let { - storeBaseManageDao.updateStoreBaseInfo( + ) + } + return true + } + + override fun deleteStoreInnerPipeline( + userId: String, + storeType: StoreTypeEnum?, + storeCode: String?, + excludeProjectCode: String? + ): Boolean { + Executors.newFixedThreadPool(1).submit { + logger.info("begin deleteStoreInnerPipeline!!") + var offset = 0 + do { + // 查询组件内置流水线信息记录 + val storePipelineRelRecords = storePipelineRelDao.getStorePipelineRelRecords( dslContext = dslContext, - updateStoreBaseDataPO = UpdateStoreBaseDataPO( - id = storeId, - status = storeStatus, - modifier = userId - ) + offset = offset, + limit = pageSize, + storeType = storeType, + storeCode = storeCode ) - } + storePipelineRelRecords?.forEach { storePipelineRelRecord -> + var initProjectCode = storePipelineRelRecord.projectCode + if (excludeProjectCode == initProjectCode) { + // 如果内置流水线的项目属于要排除的项目,则不删除该内置流水线 + return@forEach + } + storePipelineBuildRelDao.deleteStorePipelineBuildRelByPipelineId( + dslContext, + storePipelineRelRecord.pipelineId + ) + storePipelineRelDao.deleteStorePipelineRelById(dslContext, storePipelineRelRecord.id) + if (initProjectCode.isNullOrBlank()) { + initProjectCode = storeProjectRelDao.getInitProjectCodeByStoreCode( + dslContext = dslContext, + storeCode = storePipelineRelRecord.storeCode, + storeType = storePipelineRelRecord.storeType + ) + } + // 调接口删除内置流水线 + client.get(ServicePipelineResource::class).delete( + userId = userId, + pipelineId = storePipelineRelRecord.pipelineId, + channelCode = ChannelCode.AM, + projectId = initProjectCode, + checkFlag = false + ) + } + offset += pageSize + } while (storePipelineRelRecords?.size == pageSize) + logger.info("end deleteStoreInnerPipeline!!") } return true } @@ -381,6 +357,165 @@ class StorePipelineServiceImpl @Autowired constructor( } } + private fun handleStorePublicPipelineModel( + storeType: String, + userId: String, + pipelineModel: String, + storeCode: String? = null + ) { + val str = "#{$KEY_PIPELINE_NAME}" + var model = pipelineModel + val suffix = storeCode ?: "PUBLIC" + val pipelineName = getPublicPipelineName(storeType, suffix) + val projectCode = storeInnerPipelineConfig.innerPipelineProject + if (pipelineModel.contains(str)) { + model = pipelineModel.replace( + "#{$KEY_PIPELINE_NAME}", + pipelineName + ) + } + var publicPipelineId = redisOperation.get(getPublicPipelineName(storeType, "PUBLIC")) + if (publicPipelineId.isNullOrBlank()) { + publicPipelineId = creatStorePipelineByStoreCode(storeType = storeType) + } + var pipelineId = if (storeCode != null) { + storePipelineRelDao.getStorePipelineRelByStoreCode( + dslContext = dslContext, + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType) + )?.pipelineId + } else { + publicPipelineId + } + pipelineId ?: throw ErrorCodeException( + errorCode = CommonMessageCode.ERROR_INVALID_PARAM_, + params = arrayOf(storeCode ?: getPublicPipelineName(storeType, "PUBLIC") + ) + ) + // 对已托管给公共项目的指定组件刷新内置流水线model时则给组件在公共项目下创建单独的流水线 + if (storeCode != null && pipelineId == publicPipelineId) { + pipelineId = creatStorePipelineByStoreCode( + storeCode = storeCode, + storeType = storeType + ) + + val pipelineRelRecord = storePipelineRelDao.getStorePipelineRel( + dslContext = dslContext, + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType) + ) + if (pipelineRelRecord == null) { + storePipelineRelDao.add( + dslContext = dslContext, + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType), + pipelineId = pipelineId, + projectCode = storeInnerPipelineConfig.innerPipelineProject + ) + } else { + storePipelineRelDao.updateStorePipelineProject( + dslContext = dslContext, + storeCode = storeCode, + storeType = StoreTypeEnum.valueOf(storeType), + projectCode = storeInnerPipelineConfig.innerPipelineProject, + pipelineId = pipelineId + ) + } + } + val flag = client.get(ServicePipelineSettingResource::class).getPipelineSetting( + projectId = projectCode, + pipelineId = pipelineId, + channelCode = ChannelCode.AM + ).data != null + // 公共项目直接更新 + if (flag) { + client.get(ServicePipelineSettingResource::class) + .updatePipelineModel( + userId = userId, + updatePipelineModelRequest = UpdatePipelineModelRequest( + pipelineModelVersionList = listOf( + PipelineModelVersion( + projectId = projectCode, + pipelineId = pipelineId, + creator = storeInnerPipelineConfig.innerPipelineUser, + model = model + ) + ) + ) + ) + } + } + + private fun getPublicPipelineName(storeType: String, suffix: String): String { + return "$storeType-PIPELINE-BUILD:$suffix" + } + + override fun creatStorePipelineByStoreCode( + storeCode: String?, + storeType: String + ): String { + val suffix = storeCode ?: "PUBLIC" + val pipelineName = getPublicPipelineName(storeType, suffix) + var key = "CREAT-$pipelineName" + storeCode?.let { key += "-$storeCode" } + val lock = RedisLock(redisOperation, key, 60L) + try { + lock.lock() + val innerPipelineUser = storeInnerPipelineConfig.innerPipelineUser + val innerPipelineProject = storeInnerPipelineConfig.innerPipelineProject + // 获取已创建的公共流水线 + if (storeCode.isNullOrBlank()) { + val pipelineIdConfig = businessConfigDao.get( + dslContext = dslContext, + business = StoreTypeEnum.valueOf(storeType).name, + feature = "initBuildPipeline", + businessValue = "PIPELINE_ID" + ) + pipelineIdConfig?.let { + return it.configValue + } + } + val pipelineModelConfig = businessConfigDao.get( + dslContext = dslContext, + business = StoreTypeEnum.valueOf(storeType).name, + feature = "initBuildPipeline", + businessValue = "PIPELINE_MODEL" + ) + val pipelineModel = pipelineModelConfig!!.configValue.replace( + "#{$KEY_PIPELINE_NAME}", + pipelineName + ) + val model = JsonUtil.to(pipelineModel, Model::class.java) + val pipelineId = client.get(ServicePipelineResource::class).create( + userId = innerPipelineUser, + projectId = innerPipelineProject, + pipeline = model, + channelCode = ChannelCode.AM + ).data!!.id + if (storeCode == null) { + redisOperation.set( + key = pipelineName, + value = pipelineId, + expired = false + ) + // 持久化公共组件内置流水线 + businessConfigDao.add( + dslContext = dslContext, + request = BusinessConfigRequest( + business = StoreTypeEnum.valueOf(storeType).name, + feature = "initBuildPipeline", + businessValue = "PIPELINE_ID", + configValue = pipelineId, + description = "${StoreTypeEnum.valueOf(storeType).name} init build pipeline id" + ) + ) + } + return pipelineId + } finally { + lock.unlock() + } + } + private fun updatePipelineModel( storeType: String, storeCodeList: List, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePkgRunEnvInfoServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePkgRunEnvInfoServiceImpl.kt index 94d852149a0b..3cf5fcb28845 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePkgRunEnvInfoServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StorePkgRunEnvInfoServiceImpl.kt @@ -27,12 +27,14 @@ package com.tencent.devops.store.common.service.impl import com.github.benmanes.caffeine.cache.Caffeine -import com.tencent.devops.store.common.dao.StorePkgRunEnvInfoDao +import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.store.atom.factory.AtomBusHandleFactory +import com.tencent.devops.store.common.dao.StorePkgRunEnvInfoDao +import com.tencent.devops.store.common.service.AbstractDomainService +import com.tencent.devops.store.common.service.StorePkgRunEnvInfoService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.env.StorePkgRunEnvInfo import com.tencent.devops.store.pojo.common.env.StorePkgRunEnvRequest -import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum -import com.tencent.devops.store.common.service.StorePkgRunEnvInfoService import org.jooq.DSLContext import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service @@ -70,6 +72,7 @@ class StorePkgRunEnvInfoServiceImpl @Autowired constructor( } override fun getStorePkgRunEnvInfo( + devopsEnv: String?, userId: String, storeType: StoreTypeEnum, language: String, @@ -88,7 +91,7 @@ class StorePkgRunEnvInfoServiceImpl @Autowired constructor( val atomBusHandleService = AtomBusHandleFactory.createAtomBusHandleService(language) val finalOsName = atomBusHandleService.handleOsName(osName) val finalOsArch = atomBusHandleService.handleOsArch(osName, osArch) - val key = "PkgRunEnvInfo:$storeType:$language:$finalOsName:$finalOsArch:$convertRuntimeVersion" + val key = "PkgRunEnvInfo:$devopsEnv:$storeType:$language:$finalOsName:$finalOsArch:$convertRuntimeVersion" var storePkgRunEnvInfo = pkgRunEnvInfoCache.getIfPresent(key) if (storePkgRunEnvInfo != null) { // 缓存中取到了安装包运行时环境信息则直接返回 @@ -113,6 +116,14 @@ class StorePkgRunEnvInfoServiceImpl @Autowired constructor( null } else { storePkgRunEnvInfo = storePkgRunEnvInfoDao.convert(storePkgRunEnvInfoRecord) + if (!devopsEnv.isNullOrBlank()) { + // 转换域名 + val beanName = "${devopsEnv.uppercase()}_DOMAIN_SERVICE" + if (SpringContextUtil.isBeanExist(beanName)) { + val domainService = SpringContextUtil.getBean(AbstractDomainService::class.java, beanName) + storePkgRunEnvInfo.pkgDownloadPath = domainService.convertDomain(storePkgRunEnvInfo.pkgDownloadPath) + } + } // 把安装包运行时环境信息放入缓存 pkgRunEnvInfoCache.put(key, storePkgRunEnvInfo) storePkgRunEnvInfo diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreProjectServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreProjectServiceImpl.kt index 5fe6e8d04fd4..2d6afab1338c 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreProjectServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreProjectServiceImpl.kt @@ -28,6 +28,7 @@ package com.tencent.devops.store.common.service.impl import com.tencent.devops.common.api.constant.CommonMessageCode +import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.api.util.DateTimeUtil import com.tencent.devops.common.api.util.HashUtil @@ -38,11 +39,9 @@ import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.service.utils.LogUtils import com.tencent.devops.common.web.utils.I18nUtil -import com.tencent.devops.process.api.service.ServicePipelineResource import com.tencent.devops.project.api.service.ServiceProjectResource import com.tencent.devops.repository.api.ServiceRepositoryResource -import com.tencent.devops.store.common.dao.StorePipelineBuildRelDao -import com.tencent.devops.store.common.dao.StorePipelineRelDao +import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.dao.StoreProjectRelDao import com.tencent.devops.store.common.dao.StoreStatisticDailyDao import com.tencent.devops.store.common.dao.StoreStatisticDao @@ -50,12 +49,15 @@ import com.tencent.devops.store.common.service.StoreCommonService import com.tencent.devops.store.common.service.StoreProjectService import com.tencent.devops.store.common.service.StoreUserService import com.tencent.devops.store.constant.StoreMessageCode +import com.tencent.devops.store.constant.StoreMessageCode.GET_INFO_NO_PERMISSION import com.tencent.devops.store.pojo.common.InstallStoreReq import com.tencent.devops.store.pojo.common.InstalledProjRespItem import com.tencent.devops.store.pojo.common.StoreProjectInfo import com.tencent.devops.store.pojo.common.enums.StoreProjectTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.common.statistic.StoreDailyStatisticRequest +import com.tencent.devops.store.pojo.common.test.StoreTestItem +import com.tencent.devops.store.pojo.common.test.StoreTestRequest import java.util.Date import org.jooq.DSLContext import org.jooq.impl.DSL @@ -77,9 +79,8 @@ class StoreProjectServiceImpl @Autowired constructor( private val storeStatisticDailyDao: StoreStatisticDailyDao, private val storeUserService: StoreUserService, private val redisOperation: RedisOperation, - private val storePipelineRelDao: StorePipelineRelDao, - private val storePipelineBuildRelDao: StorePipelineBuildRelDao, - private val storeCommonService: StoreCommonService + private val storeCommonService: StoreCommonService, + private val storeMemberDao: StoreMemberDao ) : StoreProjectService { /** @@ -372,12 +373,6 @@ class StoreProjectServiceImpl @Autowired constructor( override fun updateStoreInitProject(userId: String, storeProjectInfo: StoreProjectInfo): Boolean { dslContext.transaction { configuration -> val context = DSL.using(configuration) - // 获取组件当前初始化项目 - val initProjectInfo = storeProjectRelDao.getInitProjectInfoByStoreCode( - dslContext = context, - storeCode = storeProjectInfo.storeCode, - storeType = storeProjectInfo.storeType.type.toByte() - )!! // 更新组件关联初始化项目 storeProjectRelDao.updateStoreInitProject(context, userId, storeProjectInfo) val testProjectInfo = storeProjectRelDao.getUserTestProjectRelByStoreCode( @@ -388,7 +383,7 @@ class StoreProjectServiceImpl @Autowired constructor( userId = storeProjectInfo.userId ) if (testProjectInfo == null) { - storeProjectRelDao.deleteUserStoreTestProject( + storeProjectRelDao.deleteStoreProject( dslContext = context, userId = storeProjectInfo.userId, storeType = storeProjectInfo.storeType, @@ -404,22 +399,6 @@ class StoreProjectServiceImpl @Autowired constructor( type = StoreProjectTypeEnum.TEST.type.toByte() ) } - val storePipelineRel = storePipelineRelDao.getStorePipelineRel( - dslContext = context, - storeCode = storeProjectInfo.storeCode, - storeType = storeProjectInfo.storeType - ) - storePipelineRel?.let { - storePipelineRelDao.deleteStorePipelineRelById(context, storePipelineRel.id) - storePipelineBuildRelDao.deleteStorePipelineBuildRelByPipelineId(context, storePipelineRel.pipelineId) - client.get(ServicePipelineResource::class).delete( - userId = userId, - pipelineId = it.pipelineId, - channelCode = ChannelCode.AM, - projectId = initProjectInfo.projectCode, - checkFlag = false - ) - } val storeRepoHashId = storeCommonService.getStoreRepoHashIdByCode(storeProjectInfo.storeCode, storeProjectInfo.storeType) storeRepoHashId?.let { @@ -432,4 +411,73 @@ class StoreProjectServiceImpl @Autowired constructor( } return true } + + override fun saveStoreTestInfo( + userId: String, + storeType: StoreTypeEnum, + storeCode: String, + storeTestRequest: StoreTestRequest + ): Boolean { + val memberFlag = storeMemberDao.isStoreMember( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + ) + if (!memberFlag) { + throw ErrorCodeException( + errorCode = GET_INFO_NO_PERMISSION, + params = arrayOf(storeCode) + ) + } + dslContext.transaction { configuration -> + val context = DSL.using(configuration) + storeProjectRelDao.deleteStoreProject( + dslContext = context, + storeType = storeType, + storeCode = storeCode, + storeProjectType = StoreProjectTypeEnum.TEST + ) + storeTestRequest.testItems.forEach { testItem -> + storeProjectRelDao.addStoreProjectRel( + dslContext = context, + userId = userId, + storeCode = storeCode, + projectCode = testItem.projectCode, + type = StoreProjectTypeEnum.TEST.type.toByte(), + storeType = storeType.type.toByte(), + instanceId = testItem.instanceId + ) + } + } + return true + } + + override fun getStoreTestInfo(userId: String, storeType: StoreTypeEnum, storeCode: String): Set { + val memberFlag = storeMemberDao.isStoreMember( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + ) + if (!memberFlag) { + throw ErrorCodeException( + errorCode = GET_INFO_NO_PERMISSION, + params = arrayOf(storeCode) + ) + } + val testProjectInfos = storeProjectRelDao.getProjectInfoByStoreCode( + dslContext = dslContext, + storeCode = storeCode, + storeType = storeType.type.toByte(), + storeProjectType = StoreProjectTypeEnum.TEST + ) + val storeTestItems = mutableSetOf() + testProjectInfos?.forEach { testProjectInfo -> + storeTestItems.add( + StoreTestItem(testProjectInfo.projectCode, testProjectInfo.instanceId) + ) + } + return storeTestItems + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreReleaseServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreReleaseServiceImpl.kt index b1baa080bd4d..07401c0c5ce3 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreReleaseServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreReleaseServiceImpl.kt @@ -33,12 +33,15 @@ import com.tencent.devops.common.api.constant.KEY_REPOSITORY_HASH_ID import com.tencent.devops.common.api.exception.ErrorCodeException import com.tencent.devops.common.client.Client import com.tencent.devops.common.pipeline.enums.ChannelCode +import com.tencent.devops.common.redis.RedisLock +import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.common.service.utils.SpringContextUtil import com.tencent.devops.process.api.service.ServiceBuildResource import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.common.dao.StoreBaseFeatureExtQueryDao import com.tencent.devops.store.common.dao.StoreBaseManageDao import com.tencent.devops.store.common.dao.StoreBaseQueryDao +import com.tencent.devops.store.common.dao.StoreDeptRelDao import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.dao.StorePipelineBuildRelDao import com.tencent.devops.store.common.dao.StoreReleaseDao @@ -46,24 +49,31 @@ import com.tencent.devops.store.common.dao.StoreVersionLogDao import com.tencent.devops.store.common.handler.StoreCreateDataPersistHandler import com.tencent.devops.store.common.handler.StoreCreateHandlerChain import com.tencent.devops.store.common.handler.StoreCreateParamCheckHandler +import com.tencent.devops.store.common.handler.StoreCreatePreBusHandler import com.tencent.devops.store.common.handler.StoreUpdateDataPersistHandler import com.tencent.devops.store.common.handler.StoreUpdateHandlerChain import com.tencent.devops.store.common.handler.StoreUpdateParamCheckHandler import com.tencent.devops.store.common.handler.StoreUpdateParamI18nConvertHandler +import com.tencent.devops.store.common.handler.StoreUpdatePreBusHandler import com.tencent.devops.store.common.handler.StoreUpdateRunPipelineHandler import com.tencent.devops.store.common.service.StoreCommonService +import com.tencent.devops.store.common.service.StoreMediaService import com.tencent.devops.store.common.service.StoreNotifyService import com.tencent.devops.store.common.service.StorePipelineService import com.tencent.devops.store.common.service.StoreReleaseService import com.tencent.devops.store.common.service.StoreReleaseSpecBusService +import com.tencent.devops.store.common.service.StoreVisibleDeptService import com.tencent.devops.store.common.utils.StoreUtils import com.tencent.devops.store.constant.StoreMessageCode import com.tencent.devops.store.pojo.common.CLOSE import com.tencent.devops.store.pojo.common.KEY_STORE_ID +import com.tencent.devops.store.pojo.common.StoreReleaseInfoUpdateRequest import com.tencent.devops.store.pojo.common.enums.AuditTypeEnum +import com.tencent.devops.store.pojo.common.enums.DeptStatusEnum import com.tencent.devops.store.pojo.common.enums.ReleaseTypeEnum import com.tencent.devops.store.pojo.common.enums.StoreStatusEnum import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.media.StoreMediaInfoRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateRequest import com.tencent.devops.store.pojo.common.publication.StoreCreateResponse import com.tencent.devops.store.pojo.common.publication.StoreOfflineRequest @@ -87,6 +97,7 @@ import java.time.LocalDateTime class StoreReleaseServiceImpl @Autowired constructor( private val client: Client, private val dslContext: DSLContext, + private val redisOperation: RedisOperation, private val storeBaseQueryDao: StoreBaseQueryDao, private val storeBaseManageDao: StoreBaseManageDao, private val storeBaseFeatureExtQueryDao: StoreBaseFeatureExtQueryDao, @@ -94,13 +105,18 @@ class StoreReleaseServiceImpl @Autowired constructor( private val storePipelineBuildRelDao: StorePipelineBuildRelDao, private val storeVersionLogDao: StoreVersionLogDao, private val storeReleaseDao: StoreReleaseDao, + private val storeDeptRelDao: StoreDeptRelDao, private val storeCommonService: StoreCommonService, private val storeNotifyService: StoreNotifyService, private val storePipelineService: StorePipelineService, + private val storeVisibleDeptService: StoreVisibleDeptService, + private val storeMediaService: StoreMediaService, private val storeCreateParamCheckHandler: StoreCreateParamCheckHandler, + private val storeCreatePreBusHandler: StoreCreatePreBusHandler, private val storeCreateDataPersistHandler: StoreCreateDataPersistHandler, private val storeUpdateParamI18nConvertHandler: StoreUpdateParamI18nConvertHandler, private val storeUpdateParamCheckHandler: StoreUpdateParamCheckHandler, + private val storeUpdatePreBusHandler: StoreUpdatePreBusHandler, private val storeUpdateDataPersistHandler: StoreUpdateDataPersistHandler, private val storeUpdateRunPipelineHandler: StoreUpdateRunPipelineHandler, private val storeInnerPipelineConfig: StoreInnerPipelineConfig @@ -115,6 +131,7 @@ class StoreReleaseServiceImpl @Autowired constructor( logger.info("createComponent userId:$userId|storeCreateRequest:$storeCreateRequest") val handlerList = mutableListOf( storeCreateParamCheckHandler, // 参数检查处理 + storeCreatePreBusHandler, // 前置业务处理 storeCreateDataPersistHandler // 数据持久化处理 ) val bkStoreContext = storeCreateRequest.bkStoreContext @@ -133,6 +150,7 @@ class StoreReleaseServiceImpl @Autowired constructor( val handlerList = mutableListOf( storeUpdateParamI18nConvertHandler, // 参数国际化处理 storeUpdateParamCheckHandler, // 参数检查处理 + storeUpdatePreBusHandler, // 前置业务处理 storeUpdateDataPersistHandler, // 数据持久化处理 storeUpdateRunPipelineHandler // 运行内置流水线 ) @@ -153,18 +171,7 @@ class StoreReleaseServiceImpl @Autowired constructor( val storeCode = record.storeCode val storeType = StoreTypeEnum.getStoreTypeObj(record.storeType.toInt()) // 判断用户是否有查询权限 - val queryFlag = storeMemberDao.isStoreMember( - dslContext = dslContext, - userId = userId, - storeCode = storeCode, - storeType = storeType.type.toByte() - ) - if (!queryFlag) { - throw ErrorCodeException( - errorCode = StoreMessageCode.GET_INFO_NO_PERMISSION, - params = arrayOf(storeCode) - ) - } + validateUserPermission(userId, storeCode, storeType) val status = StoreStatusEnum.valueOf(record.status) // 查看当前版本之前的版本是否有已发布的,如果有已发布的版本则只是普通的升级操作而不需要审核 val isNormalUpgrade = storeCommonService.getNormalUpgradeFlag( @@ -191,6 +198,25 @@ class StoreReleaseServiceImpl @Autowired constructor( ) } + private fun validateUserPermission( + userId: String, + storeCode: String, + storeType: StoreTypeEnum + ) { + val memberFlag = storeMemberDao.isStoreMember( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + ) + if (!memberFlag) { + throw ErrorCodeException( + errorCode = StoreMessageCode.GET_INFO_NO_PERMISSION, + params = arrayOf(storeCode) + ) + } + } + override fun cancelRelease(userId: String, storeId: String): Boolean { val status = StoreStatusEnum.GROUNDING_SUSPENSION checkStoreVersionOptRight(userId, storeId, status) @@ -226,17 +252,79 @@ class StoreReleaseServiceImpl @Autowired constructor( storeType = storeType, status = recordStatus ) - val status = if (storeApproveSwitch == CLOSE || isNormalUpgrade) { - StoreStatusEnum.RELEASED - } else { - StoreStatusEnum.AUDITING - } + val status = StoreStatusEnum.EDITING checkStoreVersionOptRight( userId = userId, storeId = storeId, status = status, isNormalUpgrade = isNormalUpgrade ) + dslContext.transaction { t -> + val context = DSL.using(t) + storeBaseManageDao.updateStoreBaseInfo( + dslContext = context, + updateStoreBaseDataPO = UpdateStoreBaseDataPO( + id = storeId, + status = status, + modifier = userId + ) + ) + } + return true + } + + override fun editReleaseInfo( + userId: String, + storeId: String, + storeReleaseInfoUpdateRequest: StoreReleaseInfoUpdateRequest + ): Boolean { + val record = storeBaseQueryDao.getComponentById(dslContext, storeId) + ?: throw ErrorCodeException(errorCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(storeId)) + val storeCode = record.storeCode + val storeType = StoreTypeEnum.getStoreTypeObj(record.storeType.toInt()) + validateUserPermission(userId, storeCode, storeType) + // 保存媒体信息 + val mediaInfoList = storeReleaseInfoUpdateRequest.mediaInfoList + mediaInfoList?.let { + storeMediaService.deleteByStoreCode(userId, storeCode, storeType) + mediaInfoList.forEach { + storeMediaService.add( + userId = userId, + type = storeType, + storeMediaInfo = StoreMediaInfoRequest( + storeCode = storeCode, + mediaUrl = it.mediaUrl, + mediaType = it.mediaType.name, + modifier = userId + ) + ) + } + } + // 保存可见范围信息 + val deptInfoList = storeReleaseInfoUpdateRequest.deptInfoList + deptInfoList?.let { + deptInfoList.forEach { + // 上架过程中设置的可见范围状态默认为待审核状态 + it.status = DeptStatusEnum.APPROVING.name + } + storeVisibleDeptService.addVisibleDept( + userId = userId, + storeType = storeType, + storeCode = storeCode, + deptInfos = deptInfoList + ) + } + val recordStatus = StoreStatusEnum.valueOf(record.status) + val isNormalUpgrade = storeCommonService.getNormalUpgradeFlag( + storeCode = storeCode, + storeType = storeType, + status = recordStatus + ) + val status = if (storeApproveSwitch == CLOSE || isNormalUpgrade) { + StoreStatusEnum.RELEASED + } else { + StoreStatusEnum.AUDITING + } val storeReleaseRecord = storeVersionLogDao.getStoreVersion(dslContext, storeId)!! return handleStoreRelease( userId = userId, @@ -283,6 +371,14 @@ class StoreReleaseServiceImpl @Autowired constructor( // 清空旧版本LATEST_FLAG storeBaseManageDao.cleanLatestFlag(context, storeCode, storeType) } + storeDeptRelDao.updateDeptStatus( + dslContext = context, + storeCode = storeCode, + storeType = storeType.type.toByte(), + originStatus = DeptStatusEnum.APPROVING.status.toByte(), + newStatus = DeptStatusEnum.APPROVED.status.toByte(), + userId = userId + ) storeBaseManageDao.updateStoreBaseInfo( dslContext = context, updateStoreBaseDataPO = UpdateStoreBaseDataPO( @@ -406,18 +502,55 @@ class StoreReleaseServiceImpl @Autowired constructor( ?: throw ErrorCodeException(errorCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(storeId)) val storeType = StoreTypeEnum.getStoreTypeObj(record.storeType.toInt()) val storeReleaseSpecBusService = SpringContextUtil.getBean( - StoreReleaseSpecBusService::class.java, - StoreUtils.getReleaseSpecBusServiceBeanName(storeType) + StoreReleaseSpecBusService::class.java, StoreUtils.getReleaseSpecBusServiceBeanName(storeType) ) val status = storeReleaseSpecBusService.getStoreRunPipelineStatus(startFlag = false) - status?.let { - checkStoreVersionOptRight(userId, storeId, status) + val lock = RedisLock(redisOperation, "store:$storeId:build", 30) + try { + lock.lock() + status?.let { + checkStoreVersionOptRight(userId, storeId, status) + } + val storeCode = record.storeCode + val version = record.version + // 处理环境信息逻辑 + storeReleaseSpecBusService.doStoreEnvBus( + storeCode = storeCode, storeType = storeType, version = version, userId = userId + ) + val storeRunPipelineParam = StoreRunPipelineParam( + userId = userId, storeId = storeId + ) + storePipelineService.runPipeline(storeRunPipelineParam) + } finally { + lock.unlock() + } + return true + } + + override fun back(userId: String, storeId: String): Boolean { + val record = storeBaseQueryDao.getComponentById(dslContext, storeId) + ?: throw ErrorCodeException(errorCode = CommonMessageCode.PARAMETER_IS_INVALID, params = arrayOf(storeId)) + val recordStatus = StoreStatusEnum.valueOf(record.status) + when (recordStatus) { + StoreStatusEnum.TESTING -> { + // 测试中状态的上一步需要进行重新构建 + rebuild(userId, storeId) + } + + StoreStatusEnum.EDITING -> { + // 把组件状态置为上一步的状态 + storeBaseManageDao.updateStoreBaseInfo( + dslContext = dslContext, updateStoreBaseDataPO = UpdateStoreBaseDataPO( + id = storeId, status = StoreStatusEnum.TESTING, modifier = userId + ) + ) + } + + else -> { + // 其它状态不允许回退至上一步 + throw ErrorCodeException(errorCode = StoreMessageCode.STORE_RELEASE_STEPS_ERROR) + } } - val storeRunPipelineParam = StoreRunPipelineParam( - userId = userId, - storeId = storeId - ) - storePipelineService.runPipeline(storeRunPipelineParam) return true } @@ -538,15 +671,15 @@ class StoreReleaseServiceImpl @Autowired constructor( )?.fieldValue val testingValidPreviousStatuses = if (repositoryHashId.isNullOrBlank()) { // 传包方式可以进入测试中的状态是提交中 - listOf(StoreStatusEnum.COMMITTING) + listOf(StoreStatusEnum.COMMITTING, StoreStatusEnum.EDITING) } else { - listOf(StoreStatusEnum.BUILDING) + listOf(StoreStatusEnum.BUILDING, StoreStatusEnum.CHECKING, StoreStatusEnum.EDITING) } val releasedValidPreviousStatuses = if (isNormalUpgrade == true) { - // 普通升级可以由测试中的状态直接发布 - listOf(StoreStatusEnum.TESTING) + // 普通升级可以由测试中或者审核中的状态直接发布 + listOf(StoreStatusEnum.TESTING, StoreStatusEnum.EDITING) } else { - listOf(StoreStatusEnum.TESTING, StoreStatusEnum.AUDITING) + listOf(StoreStatusEnum.TESTING, StoreStatusEnum.EDITING, StoreStatusEnum.AUDITING) } val cancelValidPreviousStatuses = StoreStatusEnum.values().toMutableList() cancelValidPreviousStatuses.remove(StoreStatusEnum.RELEASED) @@ -558,19 +691,15 @@ class StoreReleaseServiceImpl @Autowired constructor( StoreStatusEnum.TESTING ), StoreStatusEnum.BUILD_FAIL to listOf(StoreStatusEnum.BUILDING), - StoreStatusEnum.TESTING to testingValidPreviousStatuses, StoreStatusEnum.CHECKING to listOf( StoreStatusEnum.COMMITTING, StoreStatusEnum.CHECK_FAIL, StoreStatusEnum.TESTING ), - StoreStatusEnum.CHECK_FAIL to listOf( - StoreStatusEnum.COMMITTING, - StoreStatusEnum.CHECKING, - StoreStatusEnum.CHECK_FAIL, - StoreStatusEnum.TESTING - ), - StoreStatusEnum.AUDITING to listOf(StoreStatusEnum.TESTING), + StoreStatusEnum.CHECK_FAIL to listOf(StoreStatusEnum.CHECKING), + StoreStatusEnum.TESTING to testingValidPreviousStatuses, + StoreStatusEnum.EDITING to listOf(StoreStatusEnum.TESTING), + StoreStatusEnum.AUDITING to listOf(StoreStatusEnum.EDITING), StoreStatusEnum.AUDIT_REJECT to listOf(StoreStatusEnum.AUDITING), StoreStatusEnum.RELEASED to releasedValidPreviousStatuses, StoreStatusEnum.GROUNDING_SUSPENSION to cancelValidPreviousStatuses, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreTotalStatisticServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreTotalStatisticServiceImpl.kt index aea44f573d61..c36569ee001f 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreTotalStatisticServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreTotalStatisticServiceImpl.kt @@ -392,32 +392,35 @@ class StoreTotalStatisticServiceImpl @Autowired constructor( val statisticTotal = storeStatisticTotalDao.getStatisticByStoreCode(dslContext, storeCode, storeType) val percentileValue = redisOperation.get("STORE_${StoreTypeEnum.getStoreType(storeType.toInt())}_PERCENTILE_VALUE") - if (statisticTotal != null) { - storeStatisticTotalDao.updateStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType, - downloads = downloads, - comments = comments, - score = score?.toInt(), - scoreAverage = scoreAverage, - recentExecuteNum = totalExecuteNum, - hotFlag = percentileValue?.let { totalExecuteNum >= percentileValue.toDouble() }, - recentActiveDuration = totalActiveDuration - ) - } else { - storeStatisticTotalDao.initStatisticData( - dslContext = dslContext, - storeCode = storeCode, - storeType = storeType, - downloads = downloads, - comments = comments, - score = score?.toInt(), - scoreAverage = scoreAverage, - recentExecuteNum = totalExecuteNum, - hotFlag = percentileValue?.let { totalExecuteNum >= percentileValue.toDouble() }, - recentActiveDuration = totalActiveDuration - ) + dslContext.transaction { t -> + val context = DSL.using(t) + if (statisticTotal != null) { + storeStatisticTotalDao.updateStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType, + downloads = downloads, + comments = comments, + score = score?.toInt(), + scoreAverage = scoreAverage, + recentExecuteNum = totalExecuteNum, + hotFlag = percentileValue?.let { totalExecuteNum >= percentileValue.toDouble() }, + recentActiveDuration = totalActiveDuration + ) + } else { + storeStatisticTotalDao.initStatisticData( + dslContext = context, + storeCode = storeCode, + storeType = storeType, + downloads = downloads, + comments = comments, + score = score?.toInt(), + scoreAverage = scoreAverage, + recentExecuteNum = totalExecuteNum, + hotFlag = percentileValue?.let { totalExecuteNum >= percentileValue.toDouble() }, + recentActiveDuration = totalActiveDuration + ) + } } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreVisibleDeptServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreVisibleDeptServiceImpl.kt new file mode 100644 index 000000000000..eeef1914415c --- /dev/null +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/common/service/impl/StoreVisibleDeptServiceImpl.kt @@ -0,0 +1,272 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * 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. + */ + +package com.tencent.devops.store.common.service.impl + +import com.tencent.devops.common.api.constant.CommonMessageCode +import com.tencent.devops.common.api.pojo.Result +import com.tencent.devops.common.client.Client +import com.tencent.devops.common.web.utils.I18nUtil +import com.tencent.devops.model.store.tables.TStoreDeptRel +import com.tencent.devops.project.api.service.ServiceProjectOrganizationResource +import com.tencent.devops.store.common.dao.StoreDeptRelDao +import com.tencent.devops.store.common.dao.StoreMemberDao +import com.tencent.devops.store.common.service.StoreVisibleDeptService +import com.tencent.devops.store.pojo.common.visible.UserStoreDeptInfoRequest +import com.tencent.devops.store.pojo.common.enums.DeptStatusEnum +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum +import com.tencent.devops.store.pojo.common.visible.DeptInfo +import com.tencent.devops.store.pojo.common.visible.StoreVisibleDeptResp +import org.jooq.DSLContext +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +/** + * store组件可见范围逻辑类 + * since: 2019-01-08 + */ +@Service +class StoreVisibleDeptServiceImpl @Autowired constructor( + private val dslContext: DSLContext, + private val client: Client, + private val storeDeptRelDao: StoreDeptRelDao, + private val storeMemberDao: StoreMemberDao +) : StoreVisibleDeptService { + + private val logger = LoggerFactory.getLogger(StoreVisibleDeptServiceImpl::class.java) + + /** + * 查看store组件可见范围 + */ + override fun getVisibleDept( + storeCode: String, + storeType: StoreTypeEnum, + deptStatusInfos: String? + ): Result { + val deptStatusList = deptStatusInfos?.split(",")?.map { + DeptStatusEnum.valueOf(it).status.toByte() + } + val storeDeptRelRecords = storeDeptRelDao.getDeptInfosByStoreCode( + dslContext = dslContext, + storeCode = storeCode, + storeType = storeType.type.toByte(), + deptStatusList = deptStatusList + ) + return Result( + if (storeDeptRelRecords == null) { + null + } else { + val deptInfos = mutableListOf() + storeDeptRelRecords.forEach { + deptInfos.add(DeptInfo( + deptId = it.deptId, + deptName = it.deptName, + status = DeptStatusEnum.getStatus(it.status.toInt()), + comment = it.comment + )) + } + StoreVisibleDeptResp(deptInfos) + } + ) + } + + /** + * 批量获取已经审核通过的可见范围 + */ + override fun batchGetVisibleDept( + storeCodeList: List, + storeType: StoreTypeEnum + ): Result>> { + val ret = hashMapOf>() + val storeDeptRelRecords = storeDeptRelDao.batchList( + dslContext = dslContext, + storeCodeList = storeCodeList, + storeType = storeType.type.toByte() + ) + val tStoreDeptRel = TStoreDeptRel.T_STORE_DEPT_REL + storeDeptRelRecords?.forEach { + val list = if (ret.containsKey(it[tStoreDeptRel.STORE_CODE] as String)) { + ret[it[tStoreDeptRel.STORE_CODE] as String]!! + } else { + val tmp = mutableListOf() + ret[it[tStoreDeptRel.STORE_CODE] as String] = tmp + tmp + } + list.add(it[tStoreDeptRel.DEPT_ID] as Int) + } + return Result(ret) + } + + /** + * 设置store组件可见范围 + */ + override fun addVisibleDept( + userId: String, + storeCode: String, + deptInfos: List, + storeType: StoreTypeEnum + ): Result { + logger.info("addVisibleDept userId:$userId,storeCode:$storeCode,deptInfos:$deptInfos,storeType:$storeType") + // 判断用户是否有权限设置可见范围 + if (!storeMemberDao.isStoreAdmin( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + )) { + return I18nUtil.generateResponseDataObject( + messageCode = CommonMessageCode.PERMISSION_DENIED, + data = false, + language = I18nUtil.getLanguage(userId) + ) + } + val pendingDeptInfoList = mutableListOf() + deptInfos.forEach forEach@{ + val count = storeDeptRelDao.countByCodeAndDeptId( + dslContext = dslContext, + storeCode = storeCode, + deptId = it.deptId, + storeType = storeType.type.toByte() + ) + if (count> 0) { + return@forEach + } + pendingDeptInfoList.add(it) + } + storeDeptRelDao.batchAdd( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + deptInfoList = pendingDeptInfoList, + storeType = storeType.type.toByte() + ) + return Result(true) + } + + /** + * 删除store组件可见范围 + */ + override fun deleteVisibleDept( + userId: String, + storeCode: String, + deptIds: String, + storeType: StoreTypeEnum + ): Result { + logger.info("deleteVisibleDept userId:$userId,storeCode:$storeCode,deptIds:$deptIds,storeType:$storeType") + // 判断用户是否有权限删除可见范围 + if (!storeMemberDao.isStoreAdmin( + dslContext = dslContext, + userId = userId, + storeCode = storeCode, + storeType = storeType.type.toByte() + )) { + return I18nUtil.generateResponseDataObject( + messageCode = CommonMessageCode.PERMISSION_DENIED, + data = false, + language = I18nUtil.getLanguage(userId) + ) + } + val deptIdIntList = mutableListOf() + val deptIdStrList = deptIds.split(",") + deptIdStrList.forEach { + deptIdIntList.add(it.toInt()) + } + storeDeptRelDao.batchDelete( + dslContext = dslContext, + storeCode = storeCode, + deptIdList = deptIdIntList, + storeType = storeType.type.toByte() + ) + return Result(true) + } + + override fun checkUserInvalidVisibleStoreInfo( + userStoreDeptInfoRequest: UserStoreDeptInfoRequest + ): Boolean { + // 如果是公共组件,则无需校验与用户的可见范围 + if (!userStoreDeptInfoRequest.publicFlag) { + val isStoreMember = storeMemberDao.isStoreMember( + dslContext = dslContext, + userId = userStoreDeptInfoRequest.userId, + storeCode = userStoreDeptInfoRequest.storeCode, + storeType = userStoreDeptInfoRequest.storeType.type.toByte() + ) + return getValidStoreFlag( + isStoreMember = isStoreMember, + storeDepInfoList = userStoreDeptInfoRequest.storeDepInfoList, + userDeptIdList = userStoreDeptInfoRequest.userDeptIdList + ) + } + return true + } + + private fun getValidStoreFlag( + isStoreMember: Boolean, + storeDepInfoList: List?, + userDeptIdList: List + ): Boolean { + return if (isStoreMember) { + true + } else { + validateStoreDept(storeDepInfoList, userDeptIdList) + } + } + + private fun validateStoreDept( + storeDepInfoList: List?, + userDeptIdList: List + ): Boolean { + var flag = false + run breaking@{ + storeDepInfoList?.forEach deptEach@{ storeDepInfo -> + val storeDeptId = storeDepInfo.deptId + flag = validateDeptId(storeDeptId, userDeptIdList) + if (flag) return@breaking + } + } + return flag + } + + private fun validateDeptId( + storeDeptId: Int, + userDeptIdList: List + ): Boolean { + if (storeDeptId == 0 || userDeptIdList.contains(storeDeptId)) { + return true // 用户在组件的可见范围内 + } else { + // 判断该组件的可见范围是否设置了全公司可见 + val parentDeptInfoList = client.get(ServiceProjectOrganizationResource::class) + .getParentDeptInfos(storeDeptId.toString(), 1).data + if (null != parentDeptInfoList && parentDeptInfoList.isEmpty()) { + // 没有上级机构说明设置的可见范围是全公司 + return true // 用户在组件的可见范围内 + } + return false + } + } +} diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageCommonDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageCommonDao.kt index 252e07d262b7..08f3055972ad 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageCommonDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageCommonDao.kt @@ -159,4 +159,10 @@ class ImageCommonDao : AbstractStoreCommonDao() { override fun getStoreRepoHashIdByCode(dslContext: DSLContext, storeCode: String): String? { return null } + + override fun getStoreCodeById(dslContext: DSLContext, storeId: String): String? { + return with(TImage.T_IMAGE) { + dslContext.select(IMAGE_CODE).from(this).where(ID.eq(storeId)).fetchOne(0, String::class.java) + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageDao.kt index 900ae851929b..275215d59a2c 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/ImageDao.kt @@ -863,9 +863,12 @@ class ImageDao { val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL val templateStatusList = listOf(ImageStatusEnum.UNDERCARRIAGING.status.toByte(), ImageStatusEnum.UNDERCARRIAGED.status.toByte()) - return dslContext.selectCount().from(tImage).join(tStoreProjectRel) + return dslContext.select(DSL.countDistinct(tStoreProjectRel.PROJECT_CODE)).from(tImage).join(tStoreProjectRel) .on(tImage.IMAGE_CODE.eq(tStoreProjectRel.STORE_CODE)) - .where(tImage.IMAGE_STATUS.`in`(templateStatusList).and(tImage.CLASSIFY_ID.eq(classifyId))) + .where( + tImage.IMAGE_STATUS.`in`(templateStatusList).and(tImage.CLASSIFY_ID.eq(classifyId)) + .and(tStoreProjectRel.STORE_TYPE.eq(StoreTypeEnum.IMAGE.type.toByte())) + ) .fetchOne(0, Int::class.java)!! } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/MarketImageDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/MarketImageDao.kt index d637d6195028..39cc3c08f701 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/MarketImageDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/dao/MarketImageDao.kt @@ -58,6 +58,7 @@ import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_REPO_NAME import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_REPO_URL import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_SIZE import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_SOURCE_TYPE +import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_STATUS import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_SUMMARY import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_TAG import com.tencent.devops.store.image.dao.Constants.KEY_IMAGE_VERSION @@ -85,7 +86,7 @@ import org.jooq.Condition import org.jooq.DSLContext import org.jooq.Record import org.jooq.Record1 -import org.jooq.Record18 +import org.jooq.Record19 import org.jooq.Result import org.jooq.UpdateSetFirstStep import org.jooq.impl.DSL @@ -222,11 +223,8 @@ class MarketImageDao @Autowired constructor() { desc: Boolean?, page: Int?, pageSize: Int? - ): Result< - Record18>? { + ): Result> { val (tImage, tImageFeature, conditions) = formatConditions( keyword = keyword, imageSourceType = imageSourceType, @@ -245,6 +243,7 @@ class MarketImageDao @Autowired constructor() { tImage.CLASSIFY_ID.`as`(KEY_CLASSIFY_ID), tImage.LOGO_URL.`as`(KEY_IMAGE_LOGO_URL), tImage.VERSION.`as`(KEY_IMAGE_VERSION), + tImage.IMAGE_STATUS.`as`(KEY_IMAGE_STATUS), tImage.SUMMARY.`as`(KEY_IMAGE_SUMMARY), tImageFeature.PUBLIC_FLAG.`as`(KEY_IMAGE_FEATURE_PUBLIC_FLAG), tImageFeature.RECOMMEND_FLAG.`as`(KEY_IMAGE_FEATURE_RECOMMEND_FLAG), diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageReleaseService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageReleaseService.kt index e19ca8431041..7dc0f088eb86 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageReleaseService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageReleaseService.kt @@ -42,15 +42,14 @@ import com.tencent.devops.common.api.util.UUIDUtil import com.tencent.devops.common.client.Client import com.tencent.devops.common.pipeline.enums.ChannelCode import com.tencent.devops.common.pipeline.enums.StartType -import com.tencent.devops.common.pipeline.pojo.StoreInitPipelineReq import com.tencent.devops.common.pipeline.type.BuildType import com.tencent.devops.common.pipeline.type.docker.ImageType import com.tencent.devops.common.web.utils.I18nUtil import com.tencent.devops.image.api.ServiceImageResource import com.tencent.devops.model.store.tables.records.TImageRecord import com.tencent.devops.process.api.service.ServiceBuildResource -import com.tencent.devops.process.api.service.ServicePipelineInitResource import com.tencent.devops.project.api.service.ServiceProjectResource +import com.tencent.devops.store.common.configuration.StoreInnerPipelineConfig import com.tencent.devops.store.common.dao.BusinessConfigDao import com.tencent.devops.store.common.dao.StoreMemberDao import com.tencent.devops.store.common.dao.StorePipelineBuildRelDao @@ -59,6 +58,7 @@ import com.tencent.devops.store.common.dao.StoreProjectRelDao import com.tencent.devops.store.common.dao.StoreReleaseDao import com.tencent.devops.store.common.dao.StoreStatisticTotalDao import com.tencent.devops.store.common.service.StoreCommonService +import com.tencent.devops.store.common.service.StorePipelineService import com.tencent.devops.store.common.utils.VersionUtils import com.tencent.devops.store.constant.StoreMessageCode import com.tencent.devops.store.constant.StoreMessageCode.IMAGE_ADD_NO_PROJECT_MEMBER @@ -94,13 +94,13 @@ import com.tencent.devops.store.pojo.image.request.MarketImageRelRequest import com.tencent.devops.store.pojo.image.request.MarketImageUpdateRequest import com.tencent.devops.store.pojo.image.response.ImageAgentTypeInfo import com.tencent.devops.ticket.api.ServiceCredentialResource +import java.time.LocalDateTime +import java.util.Base64 import org.jooq.DSLContext import org.jooq.impl.DSL import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value -import java.time.LocalDateTime -import java.util.Base64 @Suppress("ALL") abstract class ImageReleaseService { @@ -161,9 +161,15 @@ abstract class ImageReleaseService { @Autowired lateinit var imageNotifyService: ImageNotifyService + @Autowired + lateinit var storePipelineService: StorePipelineService + @Autowired lateinit var client: Client + @Autowired + lateinit var storeInnerPipelineConfig: StoreInnerPipelineConfig + private val logger = LoggerFactory.getLogger(ImageReleaseService::class.java) @Value("\${store.imageApproveSwitch:close}") @@ -620,11 +626,12 @@ abstract class ImageReleaseService { val imageCode = imageRecord.imageCode val version = imageRecord.version val imagePipelineRelRecord = storePipelineRelDao.getStorePipelineRel(context, imageCode, StoreTypeEnum.IMAGE) + // 查找新增镜像时关联的项目 val projectCode = storeProjectRelDao.getInitProjectCodeByStoreCode( - context, - imageCode, - StoreTypeEnum.IMAGE.type.toByte() - ) // 查找新增镜像时关联的项目 + dslContext = context, + storeCode = imageCode, + storeType = StoreTypeEnum.IMAGE.type.toByte() + ) ?: throw ErrorCodeException(errorCode = CommonMessageCode.SYSTEM_ERROR) val ticketId = imageRecord.ticketId var userName: String? = null var password: String? = null @@ -633,7 +640,7 @@ abstract class ImageReleaseService { val encoder = Base64.getEncoder() val decoder = Base64.getDecoder() val credentialResult = client.get(ServiceCredentialResource::class).get( - projectCode!!, ticketId, + projectCode, ticketId, encoder.encodeToString(pair.publicKey) ) if (credentialResult.isNotOk() || credentialResult.data == null) { @@ -672,84 +679,51 @@ abstract class ImageReleaseService { userName?.let { startParams["registryUser"] = it } password?.let { startParams["registryPwd"] = it } imageRepoUrl?.let { startParams["registryHost"] = it } + val pipelineId: String? if (null == imagePipelineRelRecord) { - val pipelineModelConfig = businessConfigDao.get( + pipelineId = storePipelineService.creatStorePipelineByStoreCode(storeType = StoreTypeEnum.IMAGE.name) + storePipelineRelDao.add( dslContext = context, - business = StoreTypeEnum.IMAGE.name, - feature = "initBuildPipeline", - businessValue = "PIPELINE_MODEL" - ) - var pipelineModel = pipelineModelConfig!!.configValue - val pipelineName = "am-$imageCode-${UUIDUtil.generate()}" - val paramMap = mapOf("pipelineName" to pipelineName) - // 将流水线模型中的变量替换成具体的值 - paramMap.forEach { (key, value) -> - pipelineModel = pipelineModel.replace("#{$key}", value) - } - val storeInitPipelineReq = StoreInitPipelineReq( - pipelineModel = pipelineModel, - startParams = startParams + storeCode = imageCode, + storeType = StoreTypeEnum.IMAGE, + pipelineId = pipelineId, + projectCode = storeInnerPipelineConfig.innerPipelineProject ) - val storeInitPipelineResp = client.get(ServicePipelineInitResource::class) - .initStorePipeline(userId, projectCode!!, storeInitPipelineReq).data - logger.info("runCheckImagePipeline storeInitPipelineResp is:$storeInitPipelineResp") - if (null != storeInitPipelineResp) { - storePipelineRelDao.add( - dslContext = context, - storeCode = imageCode, - storeType = StoreTypeEnum.IMAGE, - pipelineId = storeInitPipelineResp.pipelineId, - projectCode = projectCode - ) - val buildId = storeInitPipelineResp.buildId - val imageStatus = if (buildId.isNullOrBlank()) { - ImageStatusEnum.CHECK_FAIL - } else { - storePipelineBuildRelDao.add( - dslContext = context, - storeId = imageId, - pipelineId = storeInitPipelineResp.pipelineId, - buildId = buildId - ) - ImageStatusEnum.CHECKING - } - marketImageDao.updateImageStatusById( - dslContext = context, - imageId = imageId, - imageStatus = imageStatus.status.toByte(), - userId = userId, - msg = null - ) - } } else { - // 触发执行流水线 - val buildIdObj = client.get(ServiceBuildResource::class).manualStartupNew( + pipelineId = imagePipelineRelRecord.pipelineId + } + // 触发执行流水线 + val startProjectCode = if (imagePipelineRelRecord == null) { + storeInnerPipelineConfig.innerPipelineProject + } else { + imagePipelineRelRecord.projectCode + } + val buildIdObj = client.get(ServiceBuildResource::class).manualStartupNew( + userId = if (imagePipelineRelRecord != null) userId else storeInnerPipelineConfig.innerPipelineUser, + projectId = startProjectCode, + pipelineId = pipelineId!!, + values = startParams, + channelCode = ChannelCode.AM, + startType = StartType.SERVICE + ).data + logger.info("the buildIdObj is:$buildIdObj") + if (null != buildIdObj) { + storePipelineBuildRelDao.add(context, imageId, pipelineId, buildIdObj.id) + marketImageDao.updateImageStatusById( + dslContext = context, + imageId = imageId, + imageStatus = ImageStatusEnum.CHECKING.status.toByte(), userId = userId, - projectId = projectCode!!, - pipelineId = imagePipelineRelRecord.pipelineId, - values = startParams, - channelCode = ChannelCode.AM, - startType = StartType.SERVICE - ).data - logger.info("the buildIdObj is:$buildIdObj") - if (null != buildIdObj) { - storePipelineBuildRelDao.add(context, imageId, imagePipelineRelRecord.pipelineId, buildIdObj.id) - marketImageDao.updateImageStatusById( - dslContext = context, - imageId = imageId, - imageStatus = ImageStatusEnum.CHECKING.status.toByte(), - userId = userId, - msg = null - ) // 验证中 - } else { - marketImageDao.updateImageStatusById( - dslContext = context, - imageId = imageId, - imageStatus = ImageStatusEnum.CHECK_FAIL.status.toByte(), - userId = userId, - msg = null - ) // 验证失败 - } + msg = null + ) // 验证中 + } else { + marketImageDao.updateImageStatusById( + dslContext = context, + imageId = imageId, + imageStatus = ImageStatusEnum.CHECK_FAIL.status.toByte(), + userId = userId, + msg = null + ) // 验证失败 } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageService.kt index a7cc8b7292e9..eaa8e5a339be 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/ImageService.kt @@ -357,6 +357,7 @@ abstract class ImageService @Autowired constructor() { StoreDecorateFactory.get(StoreDecorateFactory.Kind.HOST)?.decorate(logoUrl) as? String }, version = it[KEY_IMAGE_VERSION] as String, + status = ImageStatusEnum.getImageStatus((it.get(KEY_IMAGE_STATUS) as Byte).toInt()), summary = it[KEY_IMAGE_SUMMARY] as? String, score = statistic?.score ?: 0.toDouble(), downloads = statistic?.downloads ?: 0, @@ -450,6 +451,7 @@ abstract class ImageService @Autowired constructor() { name = it.name, code = it.code, version = it.version, + status = it.status, // 仅用于插件区分Agent/AgentLess type = "", rdType = it.rdType, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/impl/MarketImageClassifyService.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/impl/MarketImageClassifyService.kt index 9ce1d5cd3f01..9bb347773c0a 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/impl/MarketImageClassifyService.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/image/service/impl/MarketImageClassifyService.kt @@ -28,6 +28,7 @@ package com.tencent.devops.store.image.service.impl import com.tencent.devops.store.image.dao.ImageDao import com.tencent.devops.store.common.service.AbstractClassifyService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import org.jooq.DSLContext import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -44,7 +45,7 @@ class MarketImageClassifyService : AbstractClassifyService() { @Autowired private lateinit var imageDao: ImageDao - override fun getDeleteClassifyFlag(classifyId: String): Boolean { + override fun getDeleteClassifyFlag(classifyId: String, storeType: StoreTypeEnum): Boolean { // 允许删除分类是条件:1、该分类下的镜像都不处于上架状态 2、该分类下的镜像如果处于已下架状态但已经没人在用 var flag = false val releaseImageNum = imageDao.countReleaseImageNumByClassifyId(dslContext, classifyId) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/MarketTemplateDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/MarketTemplateDao.kt index c8d0eb573f8b..c315b189ac68 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/MarketTemplateDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/MarketTemplateDao.kt @@ -199,6 +199,7 @@ class MarketTemplateDao { tt.TEMPLATE_NAME, tt.TEMPLATE_CODE, tt.VERSION, + tt.TEMPLATE_STATUS, tt.TEMPLATE_RD_TYPE, tt.CLASSIFY_ID, tt.LOGO_URL, diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/StoreTemplateDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/StoreTemplateDao.kt index 6ab0c5dc80a7..43c53e9df109 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/StoreTemplateDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/StoreTemplateDao.kt @@ -232,12 +232,13 @@ class StoreTemplateDao { val tTemplate = TTemplate.T_TEMPLATE val tStoreProjectRel = TStoreProjectRel.T_STORE_PROJECT_REL val templateStatusList = listOf(TemplateStatusEnum.UNDERCARRIAGED.status.toByte()) - return dslContext.selectCount().from(tTemplate).join(tStoreProjectRel) + return dslContext.select(DSL.countDistinct(tStoreProjectRel.PROJECT_CODE)).from(tTemplate) + .join(tStoreProjectRel) .on(tTemplate.TEMPLATE_CODE.eq(tStoreProjectRel.STORE_CODE)) .where( tTemplate.TEMPLATE_STATUS.`in`(templateStatusList) .and(tTemplate.CLASSIFY_ID.eq(classifyId)) - ) - .fetchOne(0, Int::class.java)!! + .and(tStoreProjectRel.STORE_TYPE.eq(StoreTypeEnum.TEMPLATE.type.toByte())) + ).fetchOne(0, Int::class.java)!! } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/TemplateCommonDao.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/TemplateCommonDao.kt index 55ca642492b4..39e8f54d8476 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/TemplateCommonDao.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/dao/TemplateCommonDao.kt @@ -142,4 +142,10 @@ class TemplateCommonDao : AbstractStoreCommonDao() { } } } + + override fun getStoreCodeById(dslContext: DSLContext, storeId: String): String? { + return with(TTemplate.T_TEMPLATE) { + dslContext.select(TEMPLATE_CODE).from(this).where(ID.eq(storeId)).fetchOne(0, String::class.java) + } + } } diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateClassifyServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateClassifyServiceImpl.kt index 9200cd76d3d3..a85a3989d0d7 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateClassifyServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateClassifyServiceImpl.kt @@ -29,6 +29,7 @@ package com.tencent.devops.store.template.service.impl import com.tencent.devops.store.template.dao.StoreTemplateDao import com.tencent.devops.store.common.service.AbstractClassifyService +import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.template.service.MarketTemplateClassifyService import org.jooq.DSLContext import org.slf4j.LoggerFactory @@ -46,7 +47,7 @@ class MarketTemplateClassifyServiceImpl : MarketTemplateClassifyService, Abstrac @Autowired lateinit var templateDao: StoreTemplateDao - override fun getDeleteClassifyFlag(classifyId: String): Boolean { + override fun getDeleteClassifyFlag(classifyId: String, storeType: StoreTypeEnum): Boolean { // 允许删除分类是条件:1、该分类下的模板都不处于上架状态 2、该分类下的模板如果处于已下架状态但已经没人在用 var flag = false val releaseTemplateNum = templateDao.countReleaseTemplateNumByClassifyId(dslContext, classifyId) diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateServiceImpl.kt index ea281341e5bb..71485bbf8dcf 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/MarketTemplateServiceImpl.kt @@ -71,14 +71,14 @@ import com.tencent.devops.store.template.dao.TemplateCategoryRelDao import com.tencent.devops.store.template.dao.TemplateLabelRelDao import com.tencent.devops.store.pojo.common.MarketMainItemLabel import com.tencent.devops.store.pojo.atom.enums.AtomStatusEnum -import com.tencent.devops.store.pojo.common.DeptInfo +import com.tencent.devops.store.pojo.common.visible.DeptInfo import com.tencent.devops.store.pojo.common.HOTTEST import com.tencent.devops.store.pojo.common.KEY_CATEGORY_CODE import com.tencent.devops.store.pojo.common.KEY_PROJECT_CODE import com.tencent.devops.store.pojo.common.LATEST import com.tencent.devops.store.pojo.common.MarketItem import com.tencent.devops.store.pojo.common.StoreBaseInfo -import com.tencent.devops.store.pojo.common.UserStoreDeptInfoRequest +import com.tencent.devops.store.pojo.common.visible.UserStoreDeptInfoRequest import com.tencent.devops.store.pojo.common.enums.StoreTypeEnum import com.tencent.devops.store.pojo.image.enums.ImageStatusEnum import com.tencent.devops.store.pojo.template.InstallProjectTemplateDTO @@ -333,6 +333,7 @@ abstract class MarketTemplateServiceImpl @Autowired constructor() : MarketTempla name = it[tTemplate.TEMPLATE_NAME] as String, code = code, version = it[tTemplate.VERSION] as String, + status = TemplateStatusEnum.getTemplateStatus((it[tTemplate.TEMPLATE_STATUS] as Byte).toInt()), type = "", rdType = TemplateRdTypeEnum.getTemplateRdType((it[tTemplate.TEMPLATE_RD_TYPE] as Byte).toInt()), classifyCode = if (classifyMap.containsKey(classifyId)) classifyMap[classifyId] else "", diff --git a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/SampleMarketTemplateServiceImpl.kt b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/SampleMarketTemplateServiceImpl.kt index 3e0fb5e6bffa..2fc444a27730 100644 --- a/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/SampleMarketTemplateServiceImpl.kt +++ b/src/backend/ci/core/store/biz-store/src/main/kotlin/com/tencent/devops/store/template/service/impl/SampleMarketTemplateServiceImpl.kt @@ -29,7 +29,7 @@ package com.tencent.devops.store.template.service.impl import com.tencent.devops.common.api.pojo.Result import com.tencent.devops.common.pipeline.Model -import com.tencent.devops.store.pojo.common.UserStoreDeptInfoRequest +import com.tencent.devops.store.pojo.common.visible.UserStoreDeptInfoRequest import com.tencent.devops.store.template.service.SampleMarketTemplateService class SampleMarketTemplateServiceImpl : SampleMarketTemplateService, MarketTemplateServiceImpl() { diff --git a/src/backend/ci/core/ticket/api-ticket/src/main/kotlin/com/tencent/devops/ticket/pojo/CredentialWithPermission.kt b/src/backend/ci/core/ticket/api-ticket/src/main/kotlin/com/tencent/devops/ticket/pojo/CredentialWithPermission.kt index fc93c2b1e862..d57d0e50b747 100644 --- a/src/backend/ci/core/ticket/api-ticket/src/main/kotlin/com/tencent/devops/ticket/pojo/CredentialWithPermission.kt +++ b/src/backend/ci/core/ticket/api-ticket/src/main/kotlin/com/tencent/devops/ticket/pojo/CredentialWithPermission.kt @@ -55,5 +55,9 @@ data class CredentialWithPermission( @get:Schema(title = "最后更新者", required = true) val updateUser: String? = null, @get:Schema(title = "当前凭证是否允许跨项目使用", required = false) - val allowAcrossProject: Boolean = false + val allowAcrossProject: Boolean = false, + @get:Schema(title = "凭证创建者", required = true) + val createUser: String, + @get:Schema(title = "凭证创建时间", required = true) + val createTime: Long ) diff --git a/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt b/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt index c468f9aa8d54..089713515adc 100644 --- a/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt +++ b/src/backend/ci/core/ticket/biz-ticket/src/main/kotlin/com/tencent/devops/ticket/service/CredentialServiceImpl.kt @@ -404,7 +404,9 @@ class CredentialServiceImpl @Autowired constructor( use = hasUsePermission ), updateUser = it.updateUser, - allowAcrossProject = it.allowAcrossProject + allowAcrossProject = it.allowAcrossProject, + createTime = it.createdTime.timestamp(), + createUser = it.credentialUserId ) } return SQLPage(count, credentialList) @@ -529,7 +531,9 @@ class CredentialServiceImpl @Autowired constructor( hasViewPermission, hasEditPermission ), - updateUser = credentialRecord.updateUser + updateUser = credentialRecord.updateUser, + createTime = credentialRecord.createdTime.timestamp(), + createUser = credentialRecord.credentialUserId ) } @@ -587,7 +591,9 @@ class CredentialServiceImpl @Autowired constructor( hasEditPermission ), updateUser = credentialRecord.updateUser, - allowAcrossProject = credentialRecord.allowAcrossProject + allowAcrossProject = credentialRecord.allowAcrossProject, + createTime = credentialRecord.createdTime.timestamp(), + createUser = credentialRecord.credentialUserId ) } diff --git a/src/backend/ci/core/worker/worker-agent/src/test/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecoratorTest.kt b/src/backend/ci/core/worker/worker-agent/src/test/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecoratorTest.kt index 49d7a9f63091..1087cd404f6d 100644 --- a/src/backend/ci/core/worker/worker-agent/src/test/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecoratorTest.kt +++ b/src/backend/ci/core/worker/worker-agent/src/test/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecoratorTest.kt @@ -50,12 +50,12 @@ class TaskExecuteExceptionDecoratorTest { } @Test - fun testMaybeThirdPartyException() { + fun testMaybeRemoteServiceException() { RemoteServiceException("Service error").let { throwable -> val trustedException = TaskExecuteExceptionDecorator.decorate(throwable) assertNotNull(trustedException) assertEquals(throwable, trustedException.cause) - assertEquals(ErrorType.THIRD_PARTY, trustedException.errorType) + assertEquals(ErrorType.SYSTEM, trustedException.errorType) } } diff --git a/src/backend/ci/core/worker/worker-common/src/main/java/com/tencent/process/BkProcessTree.java b/src/backend/ci/core/worker/worker-common/src/main/java/com/tencent/process/BkProcessTree.java index 99a1c1c18784..1b190197b83b 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/java/com/tencent/process/BkProcessTree.java +++ b/src/backend/ci/core/worker/worker-common/src/main/java/com/tencent/process/BkProcessTree.java @@ -624,24 +624,29 @@ public synchronized EnvVars getEnvironmentVariables() { } private static final class UnixReflection { - private static final Field PID_FIELD; - private static final Method DESTROY_PROCESS; + private static Field PID_FIELD; + private static Method DESTROY_PROCESS; private UnixReflection() { } + // TODO: 升级到JDK17后,这里可以使用 java.lang.Process 重构 public static void destroy(int pid, boolean forceFlag) throws IllegalAccessException, InvocationTargetException { - if (isPreJava8()) { - DESTROY_PROCESS.invoke((Object) null, pid); + if (isJava17()) { + BkProcessTree.log("Killing by jdk17"); + destroyProcessJava17(pid, forceFlag); } else { + BkProcessTree.log("Killing by jdk8"); DESTROY_PROCESS.invoke((Object) null, pid, forceFlag); } - } - private static boolean isPreJava8() { - int javaVersionAsAnInteger = Integer.parseInt(System.getProperty("java.version").replaceAll("\\.", "").replaceAll("_", "").substring(0, 2)); - return javaVersionAsAnInteger < 18; + private static boolean isJava17() { + String javaVersion = System.getProperty("java.version"); + if (javaVersion.startsWith("17")) { + return true; + } + return false; } static { @@ -650,17 +655,51 @@ private static boolean isPreJava8() { Class clazz = Class.forName("java.lang.UNIXProcess"); PID_FIELD = clazz.getDeclaredField("pid"); PID_FIELD.setAccessible(true); - if (isPreJava8()) { - DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", Integer.TYPE); + DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", Integer.TYPE, Boolean.TYPE); + DESTROY_PROCESS.setAccessible(true); + } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) { + if (isJava17()) { + BkProcessTree.log("java17 ignore java8 class error"); } else { - DESTROY_PROCESS = clazz.getDeclaredMethod("destroyProcess", Integer.TYPE, Boolean.TYPE); + x = new LinkageError(); + x.initCause(e); + throw x; } + } + } - DESTROY_PROCESS.setAccessible(true); - } catch (ClassNotFoundException | NoSuchFieldException | NoSuchMethodException e) { - x = new LinkageError(); - x.initCause(e); - throw x; + private static void destroyProcessJava17(int pid, boolean forceFlag) { + try { + // 获取ProcessHandle类 + Class processHandleClass = Class.forName("java.lang.ProcessHandle"); + + // 获取ProcessHandle.of方法 + Method ofMethod = processHandleClass.getMethod("of", long.class); + Object optionalProcessHandle = ofMethod.invoke(null, pid); + + // 获取Optional.isPresent方法 + Class optionalClass = Class.forName("java.util.Optional"); + Method isPresentMethod = optionalClass.getMethod("isPresent"); + boolean isPresent = (boolean) isPresentMethod.invoke(optionalProcessHandle); + + if (isPresent) { + // 获取Optional.get方法 + Method getMethod = optionalClass.getMethod("get"); + Object processHandle = getMethod.invoke(optionalProcessHandle); + + // 获取ProcessHandle.destroy方法 + Method destroyMethod; + if (forceFlag) { + destroyMethod = processHandleClass.getMethod("destroyForcibly"); + } else { + destroyMethod = processHandleClass.getMethod("destroy"); + } + destroyMethod.invoke(processHandle); + } else { + BkProcessTree.log("Failed to terminate pid=" + pid + "no present"); + } + } catch (Exception e) { + BkProcessTree.log("Failed to terminate pid=" + pid, e); } } } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Constants.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Constants.kt index 4d8ff2a136a2..b4f80a01f3c8 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Constants.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Constants.kt @@ -65,7 +65,7 @@ const val LOG_SUBTAG_FINISH_FLAG = "##subTagFinish##" const val LOG_MESSAGE_LENGTH_LIMIT = 16 * 1024 // 16KB -const val LOG_TASK_LINE_LIMIT = 1000000 +const val LOG_TASK_LINE_LIMIT = 100000 const val LOG_FILE_LENGTH_LIMIT = 1073741824 // 1 GB = 1073741824 Byte diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Runner.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Runner.kt index 9fa3abb45827..38cad025680f 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Runner.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/Runner.kt @@ -67,11 +67,11 @@ import com.tencent.devops.worker.common.task.TaskFactory import com.tencent.devops.worker.common.utils.CredentialUtils import com.tencent.devops.worker.common.utils.KillBuildProcessTree import com.tencent.devops.worker.common.utils.ShellUtil -import org.slf4j.LoggerFactory import java.io.File import java.io.FileNotFoundException import java.io.IOException import kotlin.system.exitProcess +import org.slf4j.LoggerFactory object Runner { @@ -117,6 +117,7 @@ object Runner { messageCode = PARAMETER_ERROR, language = AgentEnv.getLocaleLanguage() ) + ":${ignore.message}" + is FileNotFoundException, is IOException -> { MessageUtil.getMessageByLocale( messageCode = RUN_AGENT_WITHOUT_PERMISSION, @@ -124,6 +125,7 @@ object Runner { params = arrayOf("${ignore.message}") ) } + else -> MessageUtil.getMessageByLocale( messageCode = UNKNOWN_ERROR, language = AgentEnv.getLocaleLanguage() @@ -237,10 +239,9 @@ object Runner { logger.info("Start to execute the task($buildTask)") when (buildTask.status) { BuildTaskStatus.DO -> { - Preconditions.checkNotNull( - obj = buildTask.taskId, - exception = RemoteServiceException("Not valid build elementId") - ) + Preconditions.checkNotNull(buildTask.taskId) { + RemoteServiceException("Not valid build elementId") + } // 处理task和job级别的上下文 combineVariables(buildTask, buildVariables) val task = TaskFactory.create(buildTask.type ?: "empty") @@ -274,6 +275,7 @@ object Runner { waitCount = 0 } } + BuildTaskStatus.WAIT -> { var sleepStep = waitCount++ / windows if (sleepStep <= 0) { @@ -283,6 +285,7 @@ object Runner { logger.info("WAIT $sleepMills ms") Thread.sleep(sleepMills) } + BuildTaskStatus.END -> break@loop } } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecorator.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecorator.kt index 09d36343ad72..ab7082469f93 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecorator.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/exception/TaskExecuteExceptionDecorator.kt @@ -102,8 +102,8 @@ class RemoteServiceExceptionD : ExceptionDecorator { override fun decorate(exception: RemoteServiceException): TaskExecuteException { return TaskExecuteException( errorMsg = "THIRD PARTY response error: ${exception.message}", - errorType = ErrorType.THIRD_PARTY, - errorCode = ErrorCode.THIRD_PARTY_INTERFACE_ERROR, + errorType = ErrorType.SYSTEM, + errorCode = ErrorCode.SYSTEM_SERVICE_ERROR, cause = exception ) } diff --git a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/logger/LoggerService.kt b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/logger/LoggerService.kt index 7fcad41c9c4f..33b4aa6584cf 100644 --- a/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/logger/LoggerService.kt +++ b/src/backend/ci/core/worker/worker-common/src/main/kotlin/com/tencent/devops/worker/common/logger/LoggerService.kt @@ -305,7 +305,7 @@ object LoggerService { ) this.uploadQueue.put( logMessage.copy( - message = "Printed logs cannot exceed 1 million lines. " + + message = "Printed logs cannot exceed 100,000 lines. " + "Please download logs to view." ) ) diff --git a/src/frontend/.husky/commit-msg b/src/frontend/.husky/commit-msg new file mode 100644 index 000000000000..0398b7a8324e --- /dev/null +++ b/src/frontend/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit ${1} diff --git a/src/frontend/.husky/pre-commit b/src/frontend/.husky/pre-commit new file mode 100644 index 000000000000..cb2c84d5c3c6 --- /dev/null +++ b/src/frontend/.husky/pre-commit @@ -0,0 +1 @@ +pnpm lint-staged diff --git a/src/frontend/bk-permission/package.json b/src/frontend/bk-permission/package.json index 4c1e9b9707d9..7f5a922bcc18 100644 --- a/src/frontend/bk-permission/package.json +++ b/src/frontend/bk-permission/package.json @@ -1,6 +1,6 @@ { "name": "bk-permission", - "version": "0.1.4", + "version": "0.1.5", "description": "", "main": "./dist/main.js", "scripts": { @@ -48,9 +48,7 @@ "webpack": "~5.76.1" }, "peerDependencies": { - "vue": "~2.7.16" - }, - "dependencies": { - "axios": "^1.7.2" + "vue": "~2.7.16", + "axios": "0.28.0" } } \ No newline at end of file diff --git a/src/frontend/bk-permission/src/components/children/permission-manage/group-aside.vue b/src/frontend/bk-permission/src/components/children/permission-manage/group-aside.vue index 0c7f42dbb2cf..f85a721dfe36 100644 --- a/src/frontend/bk-permission/src/components/children/permission-manage/group-aside.vue +++ b/src/frontend/bk-permission/src/components/children/permission-manage/group-aside.vue @@ -322,17 +322,11 @@ export default { case 'change_group_detail_tab': this.$emit('change-group-detail-tab', data.data.tab) break; - case 'submit_edit_group_perm': { - const groupId = data.data.id; - this.syncGroupPermissions(groupId) - break; - } - case 'submit_add_group_perm': { - const groupId = data.data.id; - this.syncGroupPermissions(groupId) - break; - } - case 'submit_delete_group_perm': { + case 'submit_add_group_perm': + case 'submit_delete_group_perm': + case 'submit_edit_group_perm': + case 'renewal_user_confirm': + case 'renewal_template_confirm': { const groupId = data.data.id; this.syncGroupPermissions(groupId) break; diff --git a/src/frontend/bk-permission/webpack.config.js b/src/frontend/bk-permission/webpack.config.js index 5df605348223..3249fe4020fe 100644 --- a/src/frontend/bk-permission/webpack.config.js +++ b/src/frontend/bk-permission/webpack.config.js @@ -31,7 +31,12 @@ module.exports = (env = {}, argv) => { amd: 'vue', root: 'Vue', }, - axios: 'axios', + axios: { + commonjs: 'axios', + commonjs2: 'axios', + amd: 'axios', + root: 'Axios', + }, }, module: { rules: [ diff --git a/src/frontend/bk-pipeline/package.json b/src/frontend/bk-pipeline/package.json index 45cc7dc5033c..e90f386fb413 100644 --- a/src/frontend/bk-pipeline/package.json +++ b/src/frontend/bk-pipeline/package.json @@ -67,7 +67,7 @@ "url": "git+https://github.com/Tencent/bk-ci.git" }, "peerDependencies": { - "bk-magic-vue": "2.5.9-beta.9", + "bk-magic-vue": "2.5.9-beta.41", "vue": "~2.7.16" }, "keywords": [ diff --git a/src/frontend/common-lib/docs.ts b/src/frontend/common-lib/docs.js similarity index 94% rename from src/frontend/common-lib/docs.ts rename to src/frontend/common-lib/docs.js index 623e72eb4e96..9c43022355f8 100644 --- a/src/frontend/common-lib/docs.ts +++ b/src/frontend/common-lib/docs.js @@ -21,7 +21,8 @@ const createDocs = (lang, version) => { TEMPLATE_GUIDE_DOC: `${commonPrefix}/Services/Store/ci-templates/start-new-template.md`, // 模板指引文档 TURBO_GUIDE_DOC: `${commonPrefix}/Services/Turbo/linux-tubo-speed/use_in_linux.md`, // turbo指引文档 BKAPP_NAV_OPEN_SOURCE_URL: 'https://github.com/TencentBlueKing/bk-ci', // 开源社区 - FEED_BACK_URL: `${DOCS_URL_PREFIX}/s-mart/community/question` // 问题反馈 + FEED_BACK_URL: `${DOCS_URL_PREFIX}/s-mart/community/question`, // 问题反馈 + PAC_GUIDE_DOC: `${commonPrefix}/Services/Pipeline-as-Code/01-quick-start/01-quict-start.md` // PAC快速上手文档 } const pipelineDocs = { ALIAS_BUILD_NO_DOC: `${commonPrefix}/Services/Pipeline/pipeline-edit-guide/alias-buildno.md`, // 构建号别名文档 diff --git a/src/frontend/common-lib/package.json b/src/frontend/common-lib/package.json new file mode 100644 index 000000000000..0cf81ad084fd --- /dev/null +++ b/src/frontend/common-lib/package.json @@ -0,0 +1,55 @@ +{ + "name": "common-lib", + "version": "1.0.1", + "description": "common-lib", + "private": true, + "author": "", + "devDependencies": { + "@babel/core": "^7.15.8", + "@babel/eslint-parser": "^7.15.8", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-decorators": "^7.15.8", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-proposal-function-sent": "^7.14.5", + "@babel/plugin-proposal-json-strings": "^7.14.5", + "@babel/plugin-proposal-numeric-separator": "^7.14.5", + "@babel/plugin-proposal-throw-expressions": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.14.5", + "@babel/plugin-transform-object-assign": "^7.14.5", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "@babel/preset-react": "^7.16.0", + "@babel/runtime": "^7.15.4", + "@vue/babel-preset-jsx": "^1.2.4", + "@webpack-cli/serve": "^1.7.0", + "add-asset-html-webpack-plugin": "^5.0.2", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-loader": "^8.2.2", + "babel-plugin-transform-vue-jsx": "^4.0.1", + "copy-webpack-plugin": "9.0.1", + "cross-env": "^7.0.3", + "css-loader": "^6.4.0", + "css-minimizer-webpack-plugin": "^4.2.1", + "eslint": "^7.3.1", + "eslint-config-standard": "^16.0.3", + "eslint-friendly-formatter": "~4.0.1", + "eslint-import-resolver-node": "^0.3.9", + "eslint-loader": "^2.1.2", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-standard": "^5.0.0", + "eslint-plugin-vue": "^6.2.2", + "html-webpack-plugin": "^5.3.2", + "mini-css-extract-plugin": "2.4.2", + "nx": "19.5.1", + "sass": "^1.42.1", + "sass-loader": "^12.1.0", + "style-loader": "^3.3.1", + "terser-webpack-plugin": "^5.3.6", + "vue-loader": "~15.10.0", + "vue-style-loader": "^3.0.3", + "webpack": "~5.76.1" + } +} diff --git a/src/frontend/devops-atomstore/package.json b/src/frontend/devops-atomstore/package.json index 98ae95b09ef6..46caf690dee1 100755 --- a/src/frontend/devops-atomstore/package.json +++ b/src/frontend/devops-atomstore/package.json @@ -9,7 +9,7 @@ "codemirror": "5.61.0", "core-js": "3.10.0", "dayjs": "^1.11.2", - "js-cookie": "3.0.1", + "js-cookie": "^3.0.5", "mavon-editor": "^2.10.4", "moment": "^2.29.2", "vue": "~2.7.16" diff --git a/src/frontend/devops-environment/src/components/devops/environment/node-select-dialog.vue b/src/frontend/devops-environment/src/components/devops/environment/node-select-dialog.vue index 73032ee8f48b..805eda9f6de2 100755 --- a/src/frontend/devops-environment/src/components/devops/environment/node-select-dialog.vue +++ b/src/frontend/devops-environment/src/components/devops/environment/node-select-dialog.vue @@ -145,6 +145,7 @@
{{ col.gateway }}
@@ -552,6 +553,9 @@ .node-item-agstatus { flex: 1; min-width: 82px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .prompt-operator, diff --git a/src/frontend/devops-manage/src/components/dialectPopoverTable.vue b/src/frontend/devops-manage/src/components/dialectPopoverTable.vue index b1c198fe6eb1..11bb05a72ece 100644 --- a/src/frontend/devops-manage/src/components/dialectPopoverTable.vue +++ b/src/frontend/devops-manage/src/components/dialectPopoverTable.vue @@ -75,11 +75,6 @@ const namingConventionData = [ constrainedMode: t('流程控制选项、插件入参、Job设置等流水线配置中均可使用函数'), constrainedExample: t('如变量a值为Json字符串,则bash脚本中,可以使用fromJSON读取echo “a.node is ${{fromJSON(a).node}}”') }, - { - difference: t('变量值超长'), - classic: t('仅警告未报错'), - constrainedMode: t('将报错,运行失败'), - }, { difference: t('变量 ID 规范'), classic: t('未限制'), diff --git a/src/frontend/devops-manage/src/components/project-form.vue b/src/frontend/devops-manage/src/components/project-form.vue index 89fc16141f8d..9d9e4b9fcecc 100644 --- a/src/frontend/devops-manage/src/components/project-form.vue +++ b/src/frontend/devops-manage/src/components/project-form.vue @@ -457,42 +457,66 @@ onBeforeUnmount(() => {

{{t('高级信息')}}

- - +
{{ t('权限') }}
+ + + {{ subjectScope.id === '*' ? t('全员') : subjectScope.name }} + + + +
+
+
{{ t('流水线') }}
+ - {{ subjectScope.id === '*' ? t('全员') : subjectScope.name }} - - - - - - + + + + + {{ t('CLASSIC') }} + + + {{ t('CONSTRAINED') }} + + + + - - {{ t('CLASSIC') }} - - - {{ t('CONSTRAINED') }} - - - + + + + +
@@ -520,6 +544,7 @@ onBeforeUnmount(() => { :deep(textarea) { width: auto; } + margin-top: 10px; } :deep(.bk-form-label) { font-size: 12px; @@ -564,22 +589,26 @@ onBeforeUnmount(() => { } .project-tab { width: 100%; - padding: 16px 120px 1px 24px; + padding: 20px 30px; background-color: #fff; box-shadow: 0 2px 2px 0 #00000026; - } - .advanced { - margin-bottom: 24px; - } - .title { - width: 56px; - height: 22px; - margin-bottom: 16px; - font-family: MicrosoftYaHei-Bold; - font-weight: 700; - font-size: 14px; - color: #63656E; - letter-spacing: 0; - line-height: 22px; + &.advanced { + margin-bottom: 24px; + } + .title { + margin-bottom: 16px; + font-weight: 700; + font-size: 14px; + color: #63656E; + } + .sub-title { + font-size: 14px; + border-bottom: 2px solid #DCDEE5; + margin-bottom: 15px; + } + .conventions-input { + margin-top: 10px; + max-width: 1000px; + } } diff --git a/src/frontend/devops-manage/src/components/project-user-selector.vue b/src/frontend/devops-manage/src/components/project-user-selector.vue index 0e22c16c869e..511b403027a2 100644 --- a/src/frontend/devops-manage/src/components/project-user-selector.vue +++ b/src/frontend/devops-manage/src/components/project-user-selector.vue @@ -3,7 +3,7 @@ ref="tagInputRef" class="manage-user-selector" clearable - :placeholder="t('输入授权人,选中回车进行校验')" + :placeholder="t('输入交接人,选中回车进行有效性校验')" :search-key="searchKeyArr" save-key="id" display-key="displayName" diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue new file mode 100644 index 000000000000..4ae49954db7b --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-group-tab.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue new file mode 100644 index 000000000000..a21ae94148e6 --- /dev/null +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/detail-tab-table.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-aside.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-aside.vue index f5bba016045f..4cf70fe98b91 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-aside.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-aside.vue @@ -435,18 +435,11 @@ export default { case 'change_group_detail_tab': this.$emit('change-group-detail-tab', data.data.tab) break; - case 'submit_edit_group_perm': { - const groupId = data.data.id; - this.syncGroupPermissions(groupId) - break; - } - - case 'submit_add_group_perm': { - const groupId = data.data.id; - this.syncGroupPermissions(groupId) - break; - } - case 'submit_delete_group_perm': { + case 'submit_add_group_perm': + case 'submit_delete_group_perm': + case 'submit_edit_group_perm': + case 'renewal_user_confirm': + case 'renewal_template_confirm': { const groupId = data.data.id; this.syncGroupPermissions(groupId) break; diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue index 6b29cc1a68d8..476d9814c319 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/group-tab.vue @@ -31,6 +31,7 @@ :group-name="t('用户组')" :scroll-loading="projectTable.scrollLoading" :batch-flag="batchFlag" + :is-remote-pagination="projectTable.isRemotePagination" @handle-renewal="handleRenewal" @handle-hand-over="handleHandOver" @handle-remove="handleRemove" @@ -56,7 +57,11 @@ 'manage-icon manage-icon-right-shape': !item.activeFlag, 'shape-icon': true, }" /> - + {{item.resourceTypeName}} ({{ item.resourceType }}) {{item.count}}

@@ -77,6 +82,7 @@ :group-name="item.resourceTypeName" :scroll-loading="item.scrollLoading" :batch-flag="batchFlag" + :is-remote-pagination="item.isRemotePagination" @handle-renewal="handleRenewal" @handle-hand-over="handleHandOver" @handle-remove="handleRemove" @@ -99,40 +105,13 @@ import { useI18n } from 'vue-i18n'; import { defineProps, defineEmits, computed } from 'vue'; import userGroupTable from '@/store/userGroupTable'; import TabTable from './tab-table.vue'; -import pipelineIcon from '../../../svg/color-logo-pipeline.svg'; -import codelibIcon from '../../../svg/color-logo-codelib.svg'; -import codeccIcon from '../../../svg/color-logo-codecc.svg'; -import environmentIcon from '../../../svg/color-logo-environment.svg'; -import experienceIcon from '../../../svg/color-logo-experience.svg'; -import qualityIcon from '../../../svg/color-logo-quality.svg'; -import ticketIcon from '../../../svg/color-logo-ticket.svg'; -import turboIcon from '../../../svg/color-logo-turbo.svg'; +import userDetailGroupTable from '@/store/userDetailGroupTable'; const { t } = useI18n(); const groupTableStore = userGroupTable(); const projectTable = computed(() => props.sourceList.find(item => item.resourceType == 'project')); const sourceTable= computed(() => props.sourceList.filter(item => item.resourceType != 'project')); - -const getServiceIcon = (type) => { - const iconMap = { - 'pipeline': pipelineIcon, - 'pipeline_group': pipelineIcon, - 'repertory': codelibIcon, - 'credential': ticketIcon, - 'cert': ticketIcon, - 'environment': environmentIcon, - 'env_node': pipelineIcon, - 'codecc_task': codeccIcon, - 'codecc_rule_set': codeccIcon, - 'codecc_ignore_type': codeccIcon, - 'experience_task': experienceIcon, - 'experience_group': experienceIcon, - 'rule': qualityIcon, - 'quality_group': qualityIcon, - 'pipeline_template': pipelineIcon, - } - return iconMap[type] -} +const detailGroupTable = userDetailGroupTable(); const props = defineProps({ isShowOperation: { diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue index c88cf4ec1cd3..031c7125d1ac 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/manage-all.vue @@ -269,90 +269,130 @@ + @@ -360,8 +400,8 @@ diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue index 5d1af64b0569..ca8a041b0a50 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/tab-table.vue @@ -8,7 +8,7 @@ show-overflow-tooltip :pagination="pagination" :border="border" - remote-pagination + :remote-pagination="isRemotePagination" empty-cell-text="--" selection-key="resourceCode" :checked="selectedResourceCode" @@ -37,7 +37,7 @@ {{ row.resourceName }} @@ -49,7 +49,7 @@
{{ row.unableMessage }} @@ -162,6 +162,12 @@ import { timeFormatter } from '@/common/util.ts' import useManageAside from "@/store/manageAside"; import { storeToRefs } from 'pinia'; +const LINKABLE_RESOURCE_TYPES = ['codecc_task', 'pipeline', 'pipeline_group']; +const URL_TEMPLATES = { + pipeline: (projectId, row) => `${location.origin}/console/pipeline/${projectId}/${row.resourceCode}/history/permission/?groupId=${row.groupId}`, + pipeline_group: (projectId, row) => `${location.origin}/console/pipeline/${projectId}/list/listAuth/${row.resourceCode}/${row.resourceName}?groupId=${row.groupId}`, + codecc_task: (projectId, row) => `${location.origin}/console/codecc/${projectId}/task/${row.resourceCode}/settings/authority?groupId=${row.groupId}` +}; const props = defineProps({ isShowOperation: { type: Boolean, @@ -181,6 +187,10 @@ const props = defineProps({ loading: Boolean, groupName: String, batchFlag: String, + isRemotePagination:{ + type: Boolean, + default: true, + }, }); const emit = defineEmits([ 'handleRenewal', @@ -203,19 +213,18 @@ const refTable = ref(null); const curSelectedData = ref([]); const isCurrentAll = ref(false); const selectedResourceCode = computed(() => isCurrentAll.value ? tableList.value.map(i => i.resourceCode) : curSelectedData.value.map(i => i.resourceCode)); -const resourceType = computed(() => props.resourceType); -const groupTotal = computed(() => props.groupTotal); const remainingCount = computed(()=> props.groupTotal - props.data.length); const TOOLTIPS_CONTENT = { - UNIQUE_MANAGER: t('唯一管理员,不可移出。请添加新的管理员后再移出'), - UNIQUE_OWNER: t('唯一拥有者,不可移出。请添加新的拥有者后再移出'), + UNIQUE_MANAGER: t('唯一管理员,不可直接移出。请交接或停用项目'), + UNIQUE_OWNER: t('唯一拥有者,不可直接移出。请交接或删除资源'), TEMPLATE: t('通过用户组加入,不可直接移出。如需调整,请编辑用户组') } const projectId = computed(() => route.params?.projectCode || route.query?.projectCode); const tableList = computed(() => props.data.map(item => ({ ...item, unableMessage: getUnableMessage(item), - isExpired: item.expiredAt < Date.now() && item.removeMemberButtonControl === 'OTHER' + isExpired: item.expiredAt < Date.now() && item.removeMemberButtonControl === 'OTHER', + isLinkable: LINKABLE_RESOURCE_TYPES.includes(item.resourceType) })) ); const border = ['row', 'outer']; @@ -262,7 +271,7 @@ function getUnableMessage(row){ */ function handleSelectAll({checked}) { if (checked) { - emit('getSelectList', tableList.value, resourceType.value); + emit('getSelectList', tableList.value, props.resourceType); curSelectedData.value = tableList.value; isCurrentAll.value = false; } else { @@ -274,7 +283,7 @@ function handleSelectAll({checked}) { */ function handleSelectionChange() { const selectionList = refTable.value.getSelection(); - emit('getSelectList', selectionList, resourceType.value); + emit('getSelectList', selectionList, props.resourceType); curSelectedData.value = selectionList; isCurrentAll.value = props.data.length === selectionList }; @@ -286,7 +295,7 @@ function handleSelectAllData() { if (selectLength != props.data.length) { refTable.value.toggleAllSelection(); } - emit('handleSelectAllData', resourceType.value) + emit('handleSelectAllData', props.resourceType) isCurrentAll.value = true; } /** @@ -295,55 +304,49 @@ function handleSelectAllData() { function handleClear() { refTable.value.clearSelection(); isCurrentAll.value = false; - emit('handleClear', resourceType.value); + curSelectedData.value = []; + emit('handleClear', props.resourceType); } /** * 续期按钮点击 * @param row 行数据 */ function handleRenewal(row) { - emit('handleRenewal', row, resourceType.value, refTable.value); + emit('handleRenewal', row, props.resourceType, refTable.value); } /** * 移交按钮点击 * @param row 行数据 */ function handleHandOver(row, index) { - emit('handleHandOver', row, resourceType.value, index); + emit('handleHandOver', row, props.resourceType, index); } /** * 移出按钮点击 * @param row 行数据 */ function handleRemove(row, index) { - emit('handleRemove', row, resourceType.value, index); + emit('handleRemove', row, props.resourceType, index); } /** * 加载更多 */ function handleLoadMore() { - emit('handleLoadMore', resourceType.value); + emit('handleLoadMore', props.resourceType); } function pageLimitChange(limit) { - emit('pageLimitChange',limit, resourceType.value); + emit('pageLimitChange',limit, props.resourceType); } function pageValueChange(value) { - emit('pageValueChange',value, resourceType.value); + emit('pageValueChange',value, props.resourceType); } function handleToResourcePage (row) { - if (!(['codecc_task', 'pipeline', 'pipeline_group'].includes(row.resourceType))) return - switch (row.resourceType) { - case 'pipeline': - window.open(`${location.origin}/console/pipeline/${projectId.value}/${row.resourceCode}/history/permission/?groupId=${row.groupId}`) - return - case 'pipeline_group': - window.open(`${location.origin}/console/pipeline/${projectId.value}/list/listAuth/${row.resourceCode}/${row.resourceName}?groupId=${row.groupId}`) - return - case 'codecc_task': - window.open(`${location.origin}/console/codecc/${projectId.value}/task/${row.resourceCode}/settings/authority?groupId=${row.groupId}`) - return + if (!row.isLinkable) return + const url = URL_TEMPLATES[row.resourceType]?.(projectId.value, row); + if (url) { + window.open(url); } } diff --git a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue index 96045e3116b5..3149a5f3ca27 100644 --- a/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue +++ b/src/frontend/devops-manage/src/components/user-group/components/children/permission-manage/time-limit.vue @@ -23,9 +23,9 @@ class="custom-time-select" type="number" :showControl="false" - placeholder="1-365" + placeholder="1-3650" :min="1" - :max="365" + :max="3650" @input="handleChangeCustomTime" > @@ -337,15 +340,18 @@ onMounted(() => { } } .description { - display: flex; - align-items: center; width: 416px; - height: 46px; - padding: 0 16px; + height: 58x; + padding: 6px 16px; margin: 16px 0; background: #F5F6FA; border-radius: 2px; font-size: 14px; color: #63656E; + + .warn-tip { + font-weight: 700; + color: red; + } } diff --git a/src/frontend/devops-manage/src/views/manage/project/show-project.vue b/src/frontend/devops-manage/src/views/manage/project/show-project.vue index 543aff5a4b78..3d971982e077 100644 --- a/src/frontend/devops-manage/src/views/manage/project/show-project.vue +++ b/src/frontend/devops-manage/src/views/manage/project/show-project.vue @@ -126,22 +126,33 @@ const fieldMap = [ current: 'centerName', after: 'afterCenterName', }, +]; +const propertiesFieldMap = [ { current: 'pipelineDialect', after: 'afterPipelineDialect', }, + { + current: 'pipelineNameFormat', + after: 'afterPipelineNameFormat', + }, ] const fetchDiffProjectData = () => { http.requestDiffProjectData({ englishName: projectCode, }).then((res) => { projectDiffData.value = res; - + fieldMap.forEach(field => { if (projectData.value[field.current] !== projectDiffData.value[field.after]) { projectData.value[field.after] = projectDiffData.value[field.after]; } }); + propertiesFieldMap.forEach(field => { + if (projectData.value?.properties[field.current] !== projectDiffData.value?.[field.after]) { + projectData.value[field.after] = projectDiffData.value[field.after]; + } + }); if (projectData.value?.subjectScopes.length !== projectDiffData.value?.afterSubjectScopes.length) { projectData.value['afterSubjectScopes'] = projectDiffData.value.afterSubjectScopes } else { @@ -341,184 +352,202 @@ onMounted(async () => { { padding: 24px; height: 100%; width: 100%; - } - .content-wrapper { - flex: 1; - width: 100%; overflow: auto; - background-color: #fff; - box-shadow: 0 2px 2px 0 rgb(0 0 0 / 15%); &::-webkit-scrollbar-thumb { background-color: #c4c6cc !important; border-radius: 5px !important; @@ -558,6 +581,10 @@ onMounted(async () => { height: 8px !important; } } + .content-wrapper { + flex: 1; + width: 100%; + } .project-info-content { display: flex; flex-direction: column; @@ -586,14 +613,39 @@ onMounted(async () => { } .content-main { color: #313238; - padding: 32px 48px; + } + .exception-content { + height: 100%; + background-color: #fff; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 15%); } .detail-content-form { + margin-bottom: 20px; + padding: 32px 48px 10px; + background-color: #fff; + box-shadow: 0 2px 2px 0 rgb(0 0 0 / 15%); :deep(.bk-form-label) { font-size: 12px; text-align: left; color: #979BA5; } + .title { + width: 56px; + height: 22px; + margin-bottom: 16px; + font-family: MicrosoftYaHei-Bold; + font-weight: 700; + font-size: 14px; + color: #63656E; + letter-spacing: 0; + line-height: 22px; + } + .sub-title { + font-size: 14px; + color: #63656E; + border-bottom: 2px solid #DCDEE5; + margin-bottom: 15px; + } .project-name { display: flex; align-items: center; @@ -621,6 +673,7 @@ onMounted(async () => { .item-value { display: inline-block; max-width: 800px; + white-space: pre-wrap; } .mr10 { margin-right: 10px; @@ -637,6 +690,7 @@ onMounted(async () => { background: #F5F7FA; border: 1px solid #DCDEE5; border-radius: 2px; + white-space: pre-wrap; } .scopes-diff { margin-top: 10px; diff --git a/src/frontend/devops-nav/package.json b/src/frontend/devops-nav/package.json index 1a31036e249a..e1e330a85361 100755 --- a/src/frontend/devops-nav/package.json +++ b/src/frontend/devops-nav/package.json @@ -72,10 +72,10 @@ "@blueking/platform-config": "^1.0.4", "@icon-cool/bk-icon-devops": "^0.1.13", "axios": "0.28.0", - "bk-magic-vue": "2.5.9-beta.9", + "bk-magic-vue": "2.5.9-beta.41", "bk-permission": "workspace:bk-permission", "bluebird": "^3.5.1", - "js-cookie": "^3.0.1", + "js-cookie": "^3.0.5", "sockjs-client": "1.3.0", "stompjs": "2.3.3", "vee-validate": "^2.0.3", diff --git a/src/frontend/devops-nav/src/assets/scss/index.scss b/src/frontend/devops-nav/src/assets/scss/index.scss index 1d11188ce1cc..7d7e99aecc6f 100644 --- a/src/frontend/devops-nav/src/assets/scss/index.scss +++ b/src/frontend/devops-nav/src/assets/scss/index.scss @@ -54,8 +54,3 @@ html, .bk-message-icon { min-width: 20px !important; } - -.permission-model { - min-width: 1760px; -} - diff --git a/src/frontend/devops-nav/src/components/SystemLog/index.vue b/src/frontend/devops-nav/src/components/SystemLog/index.vue index de1de13b6f8c..24004df212ec 100644 --- a/src/frontend/devops-nav/src/components/SystemLog/index.vue +++ b/src/frontend/devops-nav/src/components/SystemLog/index.vue @@ -96,11 +96,11 @@ async fetchData () { const requestHandler = this.$i18n.locale === 'en-US' ? 'fetchVersionsLogListEn' : 'fetchVersionsLogList' const res = await this.$store.dispatch(requestHandler) - this.list = res || [] + this.list = res.data || [] this.latestVerSion = (this.list.length && this.list[0].version) || '' const curVerSion = localStorage.getItem('bk_latest_version') - if (curVerSion !== this.latestVerSion && this.list.length) { + if (res.dialogVisible && curVerSion !== this.latestVerSion && this.list.length) { localStorage.setItem('bk_latest_version', this.latestVerSion) this.toggleShowLog(true) } @@ -113,6 +113,9 @@ diff --git a/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue b/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue new file mode 100644 index 000000000000..da82083309e3 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/detail-group-tab.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/permission-manage/detail-tab-table.vue b/src/frontend/devops-permission/src/components/permission-manage/detail-tab-table.vue new file mode 100644 index 000000000000..a21ae94148e6 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/detail-tab-table.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/permission-manage/group-tab.vue b/src/frontend/devops-permission/src/components/permission-manage/group-tab.vue new file mode 100644 index 000000000000..06728e790078 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/group-tab.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/permission-manage/manage-search.vue b/src/frontend/devops-permission/src/components/permission-manage/manage-search.vue new file mode 100644 index 000000000000..5f1889425b26 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/manage-search.vue @@ -0,0 +1,320 @@ + + + + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/components/permission-manage/no-permission.vue b/src/frontend/devops-permission/src/components/permission-manage/no-permission.vue new file mode 100644 index 000000000000..8a5f7d065ad5 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/no-permission.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/permission-manage/tab-table.vue b/src/frontend/devops-permission/src/components/permission-manage/tab-table.vue new file mode 100644 index 000000000000..50121a5922d7 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/tab-table.vue @@ -0,0 +1,423 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/permission-manage/time-limit.vue b/src/frontend/devops-permission/src/components/permission-manage/time-limit.vue new file mode 100644 index 000000000000..3149a5f3ca27 --- /dev/null +++ b/src/frontend/devops-permission/src/components/permission-manage/time-limit.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/frontend/devops-permission/src/components/project-user-selector.vue b/src/frontend/devops-permission/src/components/project-user-selector.vue new file mode 100644 index 000000000000..406d4805000f --- /dev/null +++ b/src/frontend/devops-permission/src/components/project-user-selector.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/frontend/devops-permission/src/css/fonts.css b/src/frontend/devops-permission/src/css/fonts.css index 537d663265d6..72bcb08d12d3 100644 --- a/src/frontend/devops-permission/src/css/fonts.css +++ b/src/frontend/devops-permission/src/css/fonts.css @@ -77,3 +77,48 @@ url("fonts/iconcool.eot?#iefix") format("embedded-opentype"); .permission-icon-warning-circle-fill:before { content: "\e112"; } +.permission-icon-down-shape:before { + content: "\e113"; +} +.permission-icon-yonghuzu:before { + content: "\e114"; +} +.permission-icon-yonghu-2:before { + content: "\e115"; +} +.permission-icon-oauth:before { + content: "\e116"; +} +.permission-icon-codelib:before { + content: "\e117"; +} +.permission-icon-user:before { + content: "\e118"; +} +.permission-icon-tishi:before { + content: "\e119"; +} +.permission-icon-permission:before { + content: "\e11a"; +} +.permission-icon-normal:before { + content: "\e11b"; +} +.permission-icon-handover:before { + content: "\e11c"; +} +.permission-icon-info:before { + content: "\e11d"; +} +.permission-icon-unknown:before { + content: "\e11e"; +} +.permission-icon-abnormal:before { + content: "\e11f"; +} +.permission-icon-warning-2:before { + content: "\e120"; +} +.permission-icon-right-shape:before { + content: "\e121"; +} diff --git a/src/frontend/devops-permission/src/css/fonts/iconcool.eot b/src/frontend/devops-permission/src/css/fonts/iconcool.eot index e0ad1eaf50ac..2f1860aa5901 100644 Binary files a/src/frontend/devops-permission/src/css/fonts/iconcool.eot and b/src/frontend/devops-permission/src/css/fonts/iconcool.eot differ diff --git a/src/frontend/devops-permission/src/css/fonts/iconcool.svg b/src/frontend/devops-permission/src/css/fonts/iconcool.svg index 909058d9aca8..84160397e5b4 100644 --- a/src/frontend/devops-permission/src/css/fonts/iconcool.svg +++ b/src/frontend/devops-permission/src/css/fonts/iconcool.svg @@ -83,6 +83,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/fonts/iconcool.ttf b/src/frontend/devops-permission/src/css/fonts/iconcool.ttf index 96486c21fbfa..10a2d78b6773 100644 Binary files a/src/frontend/devops-permission/src/css/fonts/iconcool.ttf and b/src/frontend/devops-permission/src/css/fonts/iconcool.ttf differ diff --git a/src/frontend/devops-permission/src/css/fonts/iconcool.woff b/src/frontend/devops-permission/src/css/fonts/iconcool.woff index 22c2a4e2238b..774c31a647f8 100644 Binary files a/src/frontend/devops-permission/src/css/fonts/iconcool.woff and b/src/frontend/devops-permission/src/css/fonts/iconcool.woff differ diff --git a/src/frontend/devops-permission/src/css/svg/abnormal.svg b/src/frontend/devops-permission/src/css/svg/abnormal.svg new file mode 100644 index 000000000000..c07caf86f2f6 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/abnormal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/css/svg/arrows-right.svg b/src/frontend/devops-permission/src/css/svg/arrows-right.svg new file mode 100644 index 000000000000..f5e19f245a20 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/arrows-right.svg @@ -0,0 +1,5 @@ + + +arrows-right + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-codecc.svg b/src/frontend/devops-permission/src/css/svg/color-logo-codecc.svg new file mode 100644 index 000000000000..2f8a39549e1a --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-codecc.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-codelib.svg b/src/frontend/devops-permission/src/css/svg/color-logo-codelib.svg new file mode 100644 index 000000000000..10788d3add27 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-codelib.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-environment.svg b/src/frontend/devops-permission/src/css/svg/color-logo-environment.svg new file mode 100644 index 000000000000..ec03f38e53de --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-environment.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-experience.svg b/src/frontend/devops-permission/src/css/svg/color-logo-experience.svg new file mode 100644 index 000000000000..e232253ce36c --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-experience.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-pipeline.svg b/src/frontend/devops-permission/src/css/svg/color-logo-pipeline.svg new file mode 100644 index 000000000000..6a288caa62cb --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-pipeline.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-quality.svg b/src/frontend/devops-permission/src/css/svg/color-logo-quality.svg new file mode 100644 index 000000000000..7a6c3f0df74d --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-quality.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-ticket.svg b/src/frontend/devops-permission/src/css/svg/color-logo-ticket.svg new file mode 100644 index 000000000000..81f4941dc902 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-ticket.svg @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/color-logo-turbo.svg b/src/frontend/devops-permission/src/css/svg/color-logo-turbo.svg new file mode 100644 index 000000000000..5488799a0997 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/color-logo-turbo.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/devops-permission/src/css/svg/info-circle.svg b/src/frontend/devops-permission/src/css/svg/info-circle.svg new file mode 100644 index 000000000000..a58a57fbb7c5 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/info-circle.svg @@ -0,0 +1,5 @@ + + +info-circle + + diff --git a/src/frontend/devops-permission/src/css/svg/normal.svg b/src/frontend/devops-permission/src/css/svg/normal.svg new file mode 100644 index 000000000000..1ffed3537ae8 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/normal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/css/svg/unknown.svg b/src/frontend/devops-permission/src/css/svg/unknown.svg new file mode 100644 index 000000000000..2cf940fdc14c --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/unknown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/css/svg/warninfo.svg b/src/frontend/devops-permission/src/css/svg/warninfo.svg new file mode 100644 index 000000000000..cb9a35f58afd --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/warninfo.svg @@ -0,0 +1 @@ + diff --git a/src/frontend/devops-permission/src/css/svg/warning.svg b/src/frontend/devops-permission/src/css/svg/warning.svg new file mode 100644 index 000000000000..cee3d3497b24 --- /dev/null +++ b/src/frontend/devops-permission/src/css/svg/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/http/api.ts b/src/frontend/devops-permission/src/http/api.ts index 4e7558fc5db0..b16e5d110ae9 100644 --- a/src/frontend/devops-permission/src/http/api.ts +++ b/src/frontend/devops-permission/src/http/api.ts @@ -1,9 +1,10 @@ import fetch from './fetch'; +import { OPERATE_CHANNEL } from "@/utils/constants"; -const apiPerfix = '/api'; -const applyFix = 'ms/auth/api/user/auth/apply'; -const IAM_PERFIX = '/ms/auth/api/user/auth/resource'; +const apiPerfix = '/ms/auth/api/user'; const projectPerfix = 'ms/project/api/user' +const repositoryPerfix = 'ms/repository/api/user' + export default { getUser() { return fetch.get(`${projectPerfix}/users`); @@ -11,18 +12,20 @@ export default { getAllProjectList(params) { return fetch.get(`${projectPerfix}/projects/listProjectsForApply`, params) }, - // 获取资源类型列表 + /** + * 获取资源类型列表 + */ getResourceTypesList() { - return fetch.get(`${applyFix}/listResourceTypes`); + return fetch.get(`${apiPerfix}/auth/apply/listResourceTypes`); }, // 获取动作列表 getActionsList(resourceType: any) { - return fetch.get(`${applyFix}/listActions?resourceType=${resourceType}`); + return fetch.get(`${apiPerfix}/auth/apply/listActions?resourceType=${resourceType}`); }, // 获取资源列表 getResourceList(params: any, pageInfo: any) { const { projectId, resourceType, resourceName } = params; - return fetch.get(`${IAM_PERFIX}/${projectId}/${resourceType}/listResources`, { + return fetch.get(`${apiPerfix}/auth/resource/${projectId}/${resourceType}/listResources`, { ...pageInfo, resourceName }) @@ -31,14 +34,174 @@ export default { getUserGroupList(params: any) { const { projectId } = params; delete params.projectId; - return fetch.post(`${applyFix}/${projectId}/listGroups`, params); + return fetch.post(`${apiPerfix}/auth/apply/${projectId}/listGroups`, params); }, // 申请加入用户组实体 applyToJoinGroup(params: any) { - return fetch.post(`${applyFix}/applyToJoinGroup`, params); + return fetch.post(`${apiPerfix}/auth/apply/applyToJoinGroup`, params); }, // 查询用户组权限详情 getGroupPermissionDetail(groupId: any) { - return fetch.get(`${applyFix}/${groupId}/getGroupPermissionDetail`) + return fetch.get(`${apiPerfix}/auth/apply/${groupId}/getGroupPermissionDetail`) + }, + // 获取oauth授权列表 + getOauthResource() { + return fetch.get(`${repositoryPerfix}/repositories/oauth`) + }, + // 删除oauth授权 + deleteOauth(type: any) { + return fetch.delete(`${repositoryPerfix}/repositories/oauth//delete?scmCode=${type}`) + }, + getOauthRelSource(type: any, page: Number, pageSize: Number) { + return fetch.get(`${repositoryPerfix}/repositories/oauth/relSource?scmCode=${type}&page=${page}&pageSize=${pageSize}`) + }, + refreshOauth(oauthType: any, redirectUrl: any) { + return fetch.post(`${repositoryPerfix}/repositories/oauth/reset?scmCode=${oauthType}&redirectUrl=${redirectUrl}`) + }, + /** + * 获取(代码库、流水线、部署节点)授权列表 + */ + getResourceAuthList(projectId: string, params: any) { + return fetch.post(`${apiPerfix}/auth/authorization/${projectId}/listResourceAuthorization?operateChannel=PERSONAL`, params); + }, + /** + * 批量交接用户组成员 + */ + batchHandover(projectId: string, params?: any) { + return fetch.put(`${apiPerfix}/auth/resource/member/${projectId}/batch/personal/handover`, params); + }, + /** + * 批量移除用户组成员 + */ + batchRemove(projectId: string, params?: any) { + return fetch.DELETE(`${apiPerfix}/auth/resource/member/${projectId}/batch/personal/remove`, params); + }, + /** + * 重置资源授权管理 + */ + batchOperateCheck(projectId: string, batchOperateType: string, params: any) { + return fetch.post(`${apiPerfix}/auth/resource/member/${projectId}/batch/${batchOperateType}/check`, params); + }, + /** + * 获取项目成员有权限的用户组数量 + */ + getMemberGroups(projectId: string, params: any) { + const query = new URLSearchParams({ + ...params, + }).toString(); + return fetch.get(`${apiPerfix}/auth/resource/member/${projectId}/getMemberGroupCount?${query}`); + }, + /** + * 获取项目成员有权限的用户组 + */ + getMemberGroupsDetails(projectId: string, resourceType: string, params: any) { + const query = new URLSearchParams({ + ...params, + }).toString(); + return fetch.get(`${apiPerfix}/auth/resource/group/${projectId}/${resourceType}/getMemberGroupsDetails?${query}`); + }, + /** + * 获取项目下全体成员(简单查询) + */ + async getProjectMembers(projectId: string, params?: any) { + const query = new URLSearchParams({ + ...params, + }).toString(); + return fetch.get(`${apiPerfix}/auth/resource/member/${projectId}/listProjectMembers?${query}`, { + globalError: false + }); + }, + /** + * 获取用户已加入的项目列表 + */ + fetchProjectList() { + return fetch.get(`${projectPerfix}/projects?enabled=true&queryAuthorization=true`) + }, + /** + * 获取用户有授权的项目列表 + */ + fetchProjectsWithAuthorization() { + return fetch.get(`${apiPerfix}/auth/authorization/listUserProjectsWithAuthorization`) + }, + /** + * 重置授权(代码库、流水线、部署节点) + */ + resetAuthorization(projectId: string, params: any) { + return fetch.post(`${apiPerfix}/auth/handover/${projectId}/handoverAuthorizationsApplication`, params) + }, + /** + * 校验是否可交接 + */ + checkAuthorization(projectId: string, params: any) { + return fetch.post(`${apiPerfix}/auth/authorization/${projectId}/resetResourceAuthorization`, params) + }, + /** + * 展示动作列表 + */ + getListActions(resourceType: string) { + return fetch.get(`${apiPerfix}/auth/apply/listActions?resourceType=${resourceType}`); + }, + /** + * 获取资源列表 + */ + getListResource(projectId: string, resourceType: string, params: any) { + const query = new URLSearchParams({ + ...params, + }).toString(); + return fetch.get(`${apiPerfix}/auth/resource/${projectId}/${resourceType}/listResources?${query}`); + }, + /** + * 单条续期 + */ + async renewal(projectId: string, resourceType: string, groupId: number, params: any) { + return fetch.put(`${apiPerfix}/auth/resource/group/${projectId}/${resourceType}/${groupId}/member/renewal`, params); + }, + /** + * 获取资源授权管理数量 + */ + getResourceType2CountOfHandover(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/getResourceType2CountOfHandover`, params); + }, + /** + * 获取交接单中授权相关 + */ + listAuthorizationsOfHandover(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/listAuthorizationsOfHandover`, params); + }, + /** + * 获取交接单中用户组相关 + */ + listGroupsOfHandover(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/listGroupsOfHandover`, params); + }, + /** + * 获取移交退出时的单条数据 + */ + getMemberGroupDetails(projectId: string, resourceType: string, groupId: number, memberId: string) { + return fetch.get(`${apiPerfix}/auth/resource/group/${projectId}/${resourceType}/${groupId}/getMemberGroupDetails?memberId=${memberId}`); + }, + /** + * 获取交接单列表 + */ + fetchHandoverOverviewList(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/listHandoverOverviews`, params); + }, + /** + * 处理交接审批单 + */ + handleHanoverApplication(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/handleHanoverApplication`, params); + }, + /** + * 单条退出 + */ + getIsDirectRemove(projectId: string, groupId: number, params: any) { + return fetch.DELETE(`${apiPerfix}/auth/resource/member/${projectId}/single/${groupId}/${OPERATE_CHANNEL}/remove`, params); + }, + /** + * 批量处理交接审批单 + */ + handleBatchHandovers(params: any) { + return fetch.post(`${apiPerfix}/auth/handover/batchHandleHanoverApplications`, params); } } diff --git a/src/frontend/devops-permission/src/http/fetch/index.ts b/src/frontend/devops-permission/src/http/fetch/index.ts index d5b46eadc3eb..f024448862cc 100644 --- a/src/frontend/devops-permission/src/http/fetch/index.ts +++ b/src/frontend/devops-permission/src/http/fetch/index.ts @@ -18,6 +18,7 @@ interface IHttp { head?: HttpMethod; options?: HttpMethod; patch?: HttpMethod; + DELETE?: HttpMethod; } // Content-Type @@ -27,7 +28,7 @@ const contentTypeMap = { formData: 'multipart/form-data', }; const methodsWithoutData = ['delete', 'get', 'head', 'options']; -const methodsWithData = ['post', 'put', 'patch']; +const methodsWithData = ['post', 'put', 'patch', 'DELETE']; const allMethods = [...methodsWithoutData, ...methodsWithData]; // 拼装发送请求配置 diff --git a/src/frontend/devops-permission/src/image/P4.png b/src/frontend/devops-permission/src/image/P4.png new file mode 100644 index 000000000000..d9b98bddc439 Binary files /dev/null and b/src/frontend/devops-permission/src/image/P4.png differ diff --git a/src/frontend/devops-permission/src/image/git-expire.png b/src/frontend/devops-permission/src/image/git-expire.png new file mode 100644 index 000000000000..4efe9ecaeb4b Binary files /dev/null and b/src/frontend/devops-permission/src/image/git-expire.png differ diff --git a/src/frontend/devops-permission/src/image/git.png b/src/frontend/devops-permission/src/image/git.png new file mode 100644 index 000000000000..daeb5ce57be1 Binary files /dev/null and b/src/frontend/devops-permission/src/image/git.png differ diff --git a/src/frontend/devops-permission/src/image/github-expire.png b/src/frontend/devops-permission/src/image/github-expire.png new file mode 100644 index 000000000000..4954047bf17f Binary files /dev/null and b/src/frontend/devops-permission/src/image/github-expire.png differ diff --git a/src/frontend/devops-permission/src/image/github.png b/src/frontend/devops-permission/src/image/github.png new file mode 100644 index 000000000000..3094ee83514c Binary files /dev/null and b/src/frontend/devops-permission/src/image/github.png differ diff --git a/src/frontend/devops-permission/src/image/gitlab-expire.png b/src/frontend/devops-permission/src/image/gitlab-expire.png new file mode 100644 index 000000000000..01e75145fb2f Binary files /dev/null and b/src/frontend/devops-permission/src/image/gitlab-expire.png differ diff --git a/src/frontend/devops-permission/src/image/gitlab.png b/src/frontend/devops-permission/src/image/gitlab.png new file mode 100644 index 000000000000..89c47fd9737c Binary files /dev/null and b/src/frontend/devops-permission/src/image/gitlab.png differ diff --git a/src/frontend/devops-permission/src/image/p4-expire.png b/src/frontend/devops-permission/src/image/p4-expire.png new file mode 100644 index 000000000000..f6e5d1ae8c6e Binary files /dev/null and b/src/frontend/devops-permission/src/image/p4-expire.png differ diff --git a/src/frontend/devops-permission/src/image/svn-expire.png b/src/frontend/devops-permission/src/image/svn-expire.png new file mode 100644 index 000000000000..2b4ff868a3fb Binary files /dev/null and b/src/frontend/devops-permission/src/image/svn-expire.png differ diff --git a/src/frontend/devops-permission/src/image/svn.png b/src/frontend/devops-permission/src/image/svn.png new file mode 100644 index 000000000000..2274d8b5cb58 Binary files /dev/null and b/src/frontend/devops-permission/src/image/svn.png differ diff --git a/src/frontend/devops-permission/src/image/tgit-expire.png b/src/frontend/devops-permission/src/image/tgit-expire.png new file mode 100644 index 000000000000..7831ed48f714 Binary files /dev/null and b/src/frontend/devops-permission/src/image/tgit-expire.png differ diff --git a/src/frontend/devops-permission/src/image/tgit.png b/src/frontend/devops-permission/src/image/tgit.png new file mode 100644 index 000000000000..308bc6598135 Binary files /dev/null and b/src/frontend/devops-permission/src/image/tgit.png differ diff --git a/src/frontend/devops-permission/src/router/index.ts b/src/frontend/devops-permission/src/router/index.ts index 35a7ee8dbca6..d3ddd964ed98 100644 --- a/src/frontend/devops-permission/src/router/index.ts +++ b/src/frontend/devops-permission/src/router/index.ts @@ -11,6 +11,10 @@ const MyApproval = () => import(/* webpackChunkName: "Permission" */ '../views/m const MyPermission = () => import(/* webpackChunkName: "Permission" */ '../views/my-permission/my-permission.vue'); const MyProject = () => import(/* webpackChunkName: "Permission" */ '../views/my-permission/my-project.vue'); const GroupDetail = () => import(/* webpackChunkName: "Permission" */ '../components/itsm-group-detail.vue'); +const AuthEntry = () => import(/* webpackChunkName: "Permission" */ '../views/auth/auth-entry.vue'); +const OauthHome = () => import(/* webpackChunkName: "Permission" */ '../views/auth/oauth/oauth-home.vue'); +const MyAuth = () => import(/* webpackChunkName: "Permission" */ '../views/auth/permission/my-auth.vue'); +const MyHandover = () => import(/* webpackChunkName: "Permission" */ '../views/my-permission/my-handover/index.vue'); const router = createRouter({ history: createWebHistory('permission'), routes: [ @@ -48,8 +52,39 @@ const router = createRouter({ name: 'my-project', component: MyProject, }, + { + path: 'my-handover', + name: 'my-handover', + component: MyHandover + } ], }, + { + path: '/auth', + component: AuthEntry, + children: [ + { + path: 'oauth', + name: 'oauth', + component: OauthHome, + }, + { + path: 'repertory', + name: 'repertory', + component: MyAuth, + }, + { + path: 'pipeline', + name: 'pipeline', + component: MyAuth, + }, + { + path: 'env_node', + name: 'env_node', + component: MyAuth, + } + ] + } ], }, { diff --git a/src/frontend/devops-permission/src/store/useCacheProjectCode.ts b/src/frontend/devops-permission/src/store/useCacheProjectCode.ts new file mode 100644 index 000000000000..ccd111875f03 --- /dev/null +++ b/src/frontend/devops-permission/src/store/useCacheProjectCode.ts @@ -0,0 +1,37 @@ +const CACHE_PROJECT_CODE = 'CACHE_PROJECT_CODE'; + +/** + * @param projectCode - 项目Id + */ +export function setCacheProjectCode(projectCode: string): void { + try { + localStorage.setItem(CACHE_PROJECT_CODE, projectCode) + } catch (error) { + console.error(error) + } +} + +/** + * @returns 项目代码,如果不存在则返回 null + */ +export function getCacheProjectCode(): string | null { + try { + return localStorage.getItem(CACHE_PROJECT_CODE); + } catch (error) { + console.error(error) + return null + } +} +export function removeCacheProjectCode(): void { + try { + localStorage.removeItem(CACHE_PROJECT_CODE) + } catch (error) { + console.error(error) + } +} + +export const cacheProjectCode = { + set: setCacheProjectCode, + get: getCacheProjectCode, + remove: removeCacheProjectCode, +}; \ No newline at end of file diff --git a/src/frontend/devops-permission/src/store/userDetailGroupTable.ts b/src/frontend/devops-permission/src/store/userDetailGroupTable.ts new file mode 100644 index 000000000000..bfe4682a3b62 --- /dev/null +++ b/src/frontend/devops-permission/src/store/userDetailGroupTable.ts @@ -0,0 +1,239 @@ +import http from '@/http/api'; +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { deepEqual } from "@/utils/util.js"; +import pipelineIcon from '@/css/svg/color-logo-pipeline.svg'; +import codelibIcon from '@/css/svg/color-logo-codelib.svg'; +import codeccIcon from '@/css/svg/color-logo-codecc.svg'; +import environmentIcon from '@/css/svg/color-logo-environment.svg'; +import experienceIcon from '@/css/svg/color-logo-experience.svg'; +import qualityIcon from '@/css/svg/color-logo-quality.svg'; +import ticketIcon from '@/css/svg/color-logo-ticket.svg'; +import turboIcon from '@/css/svg/color-logo-turbo.svg'; + +export enum HandoverType { + AUTHORIZATION = 'AUTHORIZATION', + GROUP = 'GROUP' +} +interface GroupTableType { + groupName?: String; + iamGroupId?: Number; + projectCode?: String; + resourceCode: String; + resourceName: String; + handoverType?: HandoverType; + handoverFrom?: String; + groupDesc?: String; + +}; +interface Pagination { + limit: number; + current: number; + count: number; +} + +interface SourceType { + activeFlag?: boolean; + count?: number; + pagination: Pagination; + resourceType: string; + resourceTypeName?: string; + tableData: GroupTableType[]; + tableLoading?: boolean; + type: HandoverType; +} + +interface CollapseListType { + resourceType: string; + resourceTypeName: string; + count: number; + type: HandoverType; +} + +interface DetailParams { + projectCode?: String; + resourceType?: String, + batchOperateType?: String; + previewConditionReq?: String; + queryChannel?: String; + page?: Number, + pageSize?: Number, + flowNo?: String +} + +export default defineStore('userDetailGroupTable', () => { + const isLoading = ref(true); + const detailSourceList = ref([]); + const collapseList = ref([]); + let currentRequestId = 0; + const detailParams = ref(); + + /** + * 获取资源授权管理数量 + */ + async function getCollapseList() { + try { + const params = detailParams.value; + const res = await http.getResourceType2CountOfHandover(params); + collapseList.value = res; + detailSourceList.value = collapseList.value.map(item => ({ + ...item, + tableLoading: false, + pagination: { count: 0, current: 1, limit: 10 }, + tableData: [], + })); + } catch (error) { + console.log(error); + } + } + + async function fetchListData(fetchFunction: (item: DetailParams) => Promise, item: SourceType) { + if (!collapseList.value.some(collapseItem => collapseItem.resourceType === item.resourceType)) { + return {}; + } + try { + const params: DetailParams = { + ...detailParams.value, + resourceType: item.resourceType, + page: item.pagination.current, + pageSize: item.pagination.limit, + }; + return await fetchFunction(params); + } catch (error) { + console.log(error); + return {}; + } + } + /** + * 获取交接单中授权相关 + * @param resourceType 资源类型 + */ + async function getAuthorizationsList(item: SourceType) { + return fetchListData(http.listAuthorizationsOfHandover, item); + } + /** + * 获取交接单中用户组相关 + * @param resourceType 资源类型 + */ + async function getGroupList(item: SourceType) { + return fetchListData(http.listGroupsOfHandover, item); + } + /** + * 获取页面数据 + */ + async function fetchDetailList(projectIdParam: DetailParams) { + // 初始化数据 + detailSourceList.value = []; + + detailParams.value = projectIdParam; + const requestId = ++currentRequestId; + + // 加载权限管理数量 + await getCollapseList(); + + try { + isLoading.value = true; + + const [authorizationItem, userGroupItem] = [ + detailSourceList.value.find(item => item.type === HandoverType.AUTHORIZATION), + detailSourceList.value.find(item => item.type === HandoverType.GROUP) + ]; + // 同时获取授权列表和用户组列表 + const [authorizationData, userGroupData] = await Promise.all([ + authorizationItem ? getAuthorizationsList(authorizationItem) : Promise.resolve(null), + userGroupItem ? getGroupList(userGroupItem) : Promise.resolve(null) + ]); + + if (currentRequestId === requestId) { + detailSourceList.value.forEach(item => { + if (authorizationData && Array.isArray(authorizationData.records) && deepEqual(item, authorizationItem)) { + item.tableData = authorizationData.records; + item.activeFlag = true; + } else if (userGroupData && Array.isArray(userGroupData.records) && deepEqual(item, userGroupItem)) { + item.tableData = userGroupData.records; + item.activeFlag = true; + } + item.pagination.count = item.count ?? 0; + }); + } + } catch (error) { + console.log(error); + } finally { + isLoading.value = false; + } + } + + async function handlePaginationChange(fetchFunction: (item: SourceType) => Promise, item: SourceType) { + try { + item.tableLoading = true; + const res = await fetchFunction(item); + item.tableData = res.records; + } catch (error) { + console.log(error); + } finally { + item.tableLoading = false; + } + } + /** + * 折叠面板调用接口获取表格数据 + */ + async function detailCollapseClick(resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (!item || !item.count || item.tableData.length) return; + + item.pagination.current = 1; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + /** + * 切换表格每页显示条数时 + */ + async function detailPageLimitChange(limit: number, resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (item) { + item.pagination.limit = limit; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + } + /** + * 切换表格分页时 + */ + async function detailPageValueChange(value: number, resourceType: string, flag: HandoverType) { + const item = detailSourceList.value.find(item => item.resourceType === resourceType && item.type === flag); + if (item) { + item.pagination.current = value; + await handlePaginationChange(flag === HandoverType.GROUP ? getGroupList : getAuthorizationsList, item); + } + } + + function getServiceIcon (type: string) { + const iconMap = { + 'pipeline': pipelineIcon, + 'pipeline_group': pipelineIcon, + 'repertory': codelibIcon, + 'credential': ticketIcon, + 'cert': ticketIcon, + 'environment': environmentIcon, + 'env_node': pipelineIcon, + 'codecc_task': codeccIcon, + 'codecc_rule_set': codeccIcon, + 'codecc_ignore_type': codeccIcon, + 'experience_task': experienceIcon, + 'experience_group': experienceIcon, + 'rule': qualityIcon, + 'quality_group': qualityIcon, + 'pipeline_template': pipelineIcon, + } + return iconMap[type] + } + + return { + isLoading, + detailSourceList, + collapseList, + fetchDetailList, + detailCollapseClick, + detailPageLimitChange, + detailPageValueChange, + getServiceIcon, + }; +}); diff --git a/src/frontend/devops-permission/src/store/userGroupTable.ts b/src/frontend/devops-permission/src/store/userGroupTable.ts new file mode 100644 index 000000000000..a48b61fd7c17 --- /dev/null +++ b/src/frontend/devops-permission/src/store/userGroupTable.ts @@ -0,0 +1,494 @@ +import http from '@/http/api'; +import { defineStore } from 'pinia'; +import { ref, reactive, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import dayjs from 'dayjs'; +import { OPERATE_CHANNEL } from "@/utils/constants"; + +interface GroupTableType { + resourceCode: string, + resourceName: string, + resourceType: string, + groupId: number; + groupName: string; + groupDesc: string; + expiredAtDisplay: string; + joinedTime: number; + expiredAt: number, + operateSource: string; + operator: string; + removeMemberButtonControl: 'OTHER' | 'TEMPLATE' | 'UNIQUE_MANAGER' | 'UNIQUE_OWNER' | 'DEPARTMENT'; + beingHandedOver: Boolean; + memberType: string; +}; +interface Pagination { + limit: number; + current: number; + count: number; +} +interface SourceType { + pagination?: Pagination; + count?: number; + isAll?: boolean; + remainingCount?: number; + resourceTypeName?: string; + resourceType: string, + hasNext?: boolean, + activeFlag?: boolean; + tableLoading?: boolean; + scrollLoading?: boolean; + isRemotePagination?: boolean; + tableData: GroupTableType[]; +} +interface SelectedDataType { + [key: string]: GroupTableType[]; +} +interface CollapseListType { + resourceType: string; + resourceTypeName: string; + count: number; +} + +interface SearchParamsType { + relatedResourceType?: string, + relatedResourceCode?: string, + action?: string, + minExpiredAt?: number, + maxExpiredAt?: number, + groupName?: string, +} + +export default defineStore('userGroupTable', () => { + const { t } = useI18n(); + const isLoading = ref(true); + const projectId = ref(); + const paginations = ref({}) + const memberId = ref(''); + const sourceList = ref([]); + const collapseList = ref([]); + + const isShowRenewal = ref(false); + const isShowHandover = ref(false); + const isShowRemove = ref(false); + const selectedData = reactive({}); + const selectSourceList = ref([]); + const selectedRow = ref(null); + const rowIndex = ref(); + const selectedTableGroupType = ref(''); + const selectedLength = ref(0); + const isPermission = ref(true); + let currentRequestId = 0; + const searchObj = ref(); + const currentTableRef = ref(); + + watch(selectedData, () => { + getSourceList() + }) + + /** + * 初始化数据 + */ + function initData() { + selectedRow.value = null; + rowIndex.value = undefined; + selectedTableGroupType.value = ''; + sourceList.value = []; + selectSourceList.value = []; + selectedLength.value = 0; + Object.keys(selectedData).forEach(key => { + delete selectedData[key]; + }); + } + /** + * 获取项目成员有权限的用户组数量 + */ + async function getCollapseList(memberId: string, projectId: string, searchObj: SearchParamsType) { + paginations.value = [] + try { + const query = { + operateChannel: OPERATE_CHANNEL, + memberId: memberId, + ...searchObj, + } + const res = await http.getMemberGroups(projectId, query); + collapseList.value = res; + if (res.length) { + isPermission.value = true; + } else { + isPermission.value = false; + } + sourceList.value = collapseList.value.map((item) => ({ + ...item, + tableLoading: false, + scrollLoading: false, + isRemotePagination: true, + tableData: [], + })) + + collapseList.value.map(i => i.resourceType).forEach(i => { + paginations.value[i] = [0, 10] + }) + } catch (error) { + console.log(error); + } + } + /** + * 获取项目成员有权限的用户组 + * @param resourceType 资源类型 + */ + async function getGroupList(resourceType: string, searchObj?: SearchParamsType) { + if (!collapseList.value.some(item => item.resourceType === resourceType)) { + return {}; + } + try { + const params = { + operateChannel: OPERATE_CHANNEL, + memberId: memberId.value, + start: paginations.value[resourceType][0], + limit: paginations.value[resourceType][1], + ...searchObj, + } + return await http.getMemberGroupsDetails(projectId.value, resourceType, params); + } catch (error) { + console.log(error); + } + } + /** + * 搜索参数处理 + */ + function getTimestamp(dateString: string) { + return dayjs(dateString).valueOf(); + } + function getParams(searchGroup: any) { + const params: SearchParamsType = {}; + + if (searchGroup?.relatedResourceType) { + params.relatedResourceType = searchGroup.relatedResourceType + } + + if (searchGroup?.relatedResourceCode) { + params.relatedResourceCode = searchGroup.relatedResourceCode + } + + if (searchGroup?.action) { + params.action = searchGroup.action + } + + if (searchGroup?.expiredAt && Object.keys(searchGroup?.expiredAt).length) { + params.minExpiredAt = getTimestamp(searchGroup.expiredAt[0]?.formatText); + params.maxExpiredAt = getTimestamp(searchGroup.expiredAt[1]?.formatText); + } + + searchGroup?.searchValue?.forEach((item: any) => { + switch (item.id) { + case 'groupName': + params.groupName = item.values[0].name; + break; + } + }) + return params; + } + /** + * 获取项目成员页面数据 + */ + async function fetchUserGroupList(memberIdParam: string, projectIdParam: string, seacrhParams: SearchParamsType) { + const params = getParams(seacrhParams); + searchObj.value = { + ...['relatedResourceType', 'relatedResourceCode', 'action', 'minExpiredAt', 'maxExpiredAt', 'groupName'] + .reduce((acc, key) => { + if (params[key]) { + acc[key] = params[key]; + } + return acc; + }, {}) + } + // 接口调用必用参数 + memberId.value = memberIdParam; + projectId.value = projectIdParam; + const requestId = ++currentRequestId; + // 初始化数据 + initData(); + // 获取项目成员有权限的用户组数量 + await getCollapseList(memberIdParam, projectIdParam, searchObj.value); + // 获取项目成员有权限的用户组的前两个表格数据 + try { + isLoading.value = true; + const resourceTypes = sourceList.value.map(i => i.resourceType).slice(0, 2); + const results = await Promise.all( + resourceTypes.map(resourceType => getGroupList(resourceType, searchObj.value)) + ); + + if (currentRequestId === requestId) { + sourceList.value.forEach((item, index) => { + if ((index === 0 || index === 1) && results[index]) { + item.tableData = results[index].records; + item.activeFlag = true; + } + }) + isLoading.value = false; + } + } catch (error: any) { + isLoading.value = false; + console.error(error); + } + } + /** + * 续期按钮点击 + * @param row 行数据 + */ + function handleRenewal(row: GroupTableType, resourceType: string, tableRef: any) { + selectedRow.value = row; + selectedTableGroupType.value = resourceType; + isShowRenewal.value = true; + currentTableRef.value = tableRef + } + /** + * 移交按钮点击 + * @param row 行数据 + */ + function handleHandOver(row: GroupTableType, resourceType: string, index: number) { + selectedRow.value = row; + rowIndex.value = index; + selectedTableGroupType.value = resourceType; + isShowHandover.value = true; + } + /** + * 退出按钮点击 + * @param row 行数据 + */ + function handleRemove(row: GroupTableType, resourceType: string, index: number) { + selectedRow.value = row; + rowIndex.value = index; + selectedTableGroupType.value = resourceType; + isShowRemove.value = true; + } + /** + * 单行续期弹窗提交 + * @param expiredAt 续期时间 + */ + async function handleUpDateRow(expiredAt: number) { + let paramTimestamp: number; + const isExpired = selectedRow.value!.expiredAt < Date.now() + if (!isExpired) { + paramTimestamp = (selectedRow.value!.expiredAt / 1000) + expiredAt * 24 * 3600 + } else { + paramTimestamp = Math.floor(Date.now() / 1000) + expiredAt * 24 * 3600 + } + try { + const params = { + expiredAt: paramTimestamp + }; + const res = await http.renewal(projectId.value, selectedRow.value!.resourceType, selectedRow.value!.groupId, params) + if (res) { + delete selectedData[selectedTableGroupType.value]; + currentTableRef.value.clearSelection(); + } + } catch (error) { + console.log(error); + } + } + /** + * 替换行数据 + */ + async function handleReplaceRow(memberId: string) { + const activeTableData = sourceList.value.find(group => group.resourceType === selectedTableGroupType.value); + if (activeTableData) { + const res = await http.getMemberGroupDetails(projectId.value, selectedRow.value!.resourceType, selectedRow.value!.groupId, memberId) + activeTableData.tableData?.splice(rowIndex.value as number, 1, res); + activeTableData.tableData = [...activeTableData.tableData]; + } + isPermission.value = sourceList.value.every(item => item.count !== 0) + handleClear(selectedTableGroupType.value); + } + /** + * 删除行数据 + */ + function handleRemoveRow() { + const current = paginations.value[selectedTableGroupType.value]; + current[2] = (current[2] ?? 0) + 1; + + const activeTableData = sourceList.value.find(group => group.resourceType === selectedTableGroupType.value); + if (activeTableData) { + activeTableData.tableData?.splice(rowIndex.value as number, 1); + activeTableData.tableData = [...activeTableData.tableData]; + activeTableData.count = activeTableData.count! - 1; + } + isPermission.value = sourceList.value.every(item => item.count !== 0) + handleClear(selectedTableGroupType.value); + } + /** + * 获取表格选择的数据 + */ + function getSelectList(selections: GroupTableType[], resourceType: string) { + let item = sourceList.value.find((item: SourceType) => item.resourceType == resourceType); + item && (item.isAll = false); + selectedData[resourceType] = selections + if (!selectedData[resourceType].length) { + delete selectedData[resourceType] + } + } + /** + * 获取选中的用户组数据 + */ + function getSourceList() { + selectedLength.value = 0; + selectSourceList.value = Object.entries(selectedData) + .map(([key, tableData]: [string, GroupTableType[]]) => { + const sourceItem = sourceList.value.find((item: SourceType) => item.resourceType == key) as SourceType; + selectedLength.value += sourceItem.isAll ? sourceItem.count! : tableData.length + return { + pagination: { + limit: 10, + current: 1, + count: sourceItem.isAll ? sourceItem.count! : tableData.length + }, + ...sourceItem, + tableData: tableData, + count: sourceItem.isAll ? sourceItem.count! : tableData.length, + ...(!sourceItem.isAll && { isRemotePagination: false }), + ...(!sourceItem.isAll && { groupIds: tableData.map(item => ({ id: item.groupId, memberType: item.memberType })) }), + }; + }); + } + /** + * 加载更多 + */ + async function handleLoadMore(resourceType: string) { + let item = sourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item?.scrollLoading) return; + const pagination = paginations.value[resourceType]; + const currentOffset = pagination[0]; + const nextOffsetAdjustment = pagination[2] || 0; + + const newOffset = currentOffset + 10 - nextOffsetAdjustment; + pagination[0] = newOffset; + + if (item) { + item.scrollLoading = true; + const res = await getGroupList(resourceType, searchObj.value); + item.scrollLoading = false; + item.tableData = [...item.tableData, ...res.records]; + if (pagination[2]) { + pagination.pop(); + } + } + } + /** + * 全量数据选择 + */ + function handleSelectAllData(resourceType: string) { + let item = sourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item) { + item.isAll = true; + selectedData[resourceType] = item.tableData.filter(i => !i.beingHandedOver) + } + } + /** + * 清除选择 + */ + function handleClear(resourceType: string) { + let item = sourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item) { + item.isAll = false; + } + delete selectedData[resourceType] + } + /** + * 折叠面板调用接口获取表格数据 + */ + async function collapseClick(resourceType: string) { + let item = sourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item) { + if (!item.count || item.tableData.length) { + return; + } else { + try { + item.tableLoading = true; + paginations.value[resourceType] = [0, 10] + const res = await getGroupList(resourceType, searchObj.value); + item.tableLoading = false; + item.tableData = res.records; + } catch (e) { + item.tableLoading = false; + } + } + } + } + /** + * 切换表格每页显示条数时 + */ + async function pageLimitChange(limit: number, resourceType: string) { + try { + let item = selectSourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item) { + paginations.value[resourceType][1] = limit; + item.tableLoading = true; + if (item.isRemotePagination) { + const res = await getGroupList(resourceType, searchObj.value) + item.tableData = res.records; + } + item.tableLoading = false; + } + } catch (error) { + console.log(error); + } + } + /** + * 切换表格分页时 + */ + async function pageValueChange(value: number, resourceType: string) { + try { + let item = selectSourceList.value.find((item: SourceType) => item.resourceType == resourceType); + if (item) { + paginations.value[resourceType][0] = (value - 1) * 10 + 1; + item.tableLoading = true; + if (item.isRemotePagination) { + const res = await getGroupList(resourceType, searchObj.value) + item.tableData = res.records; + } + item.tableLoading = false; + } + } catch (error) { + + } + } + + function clearPaginations() { + Object.keys(paginations.value).forEach(key => { + paginations.value[key] = [0, 10]; + }); + } + + + return { + isLoading, + sourceList, + collapseList, + isShowRenewal, + isShowHandover, + isShowRemove, + selectedData, + selectedLength, + selectSourceList, + selectedRow, + isPermission, + projectId, + initData, + fetchUserGroupList, + handleRenewal, + handleHandOver, + handleRemove, + getSelectList, + getSourceList, + handleLoadMore, + handleSelectAllData, + handleClear, + collapseClick, + handleReplaceRow, + handleRemoveRow, + handleUpDateRow, + pageLimitChange, + pageValueChange, + clearPaginations, + }; +}); diff --git a/src/frontend/devops-permission/src/utils/constants.js b/src/frontend/devops-permission/src/utils/constants.js new file mode 100644 index 000000000000..2822eaca540f --- /dev/null +++ b/src/frontend/devops-permission/src/utils/constants.js @@ -0,0 +1,31 @@ +export const OPERATE_CHANNEL = 'PERSONAL'; +export const TIME_FILTERS = { + 30: '1个月', + 90: '3个月', + 180: '6个月', + 360: '12个月', +}; +export const batchOperateTypes = { + handover: 'HANDOVER', + remove: 'REMOVE', +} + +export const batchMassageText = { + handover: '用户组权限已移交', + remove: '用户组已退出', +} + +export const btnTexts = { + renewal: "确定续期", + handover: "确定移交", + remove: "确定退出" +} +export const batchTitle = { + handover: "批量移交", + remove: "批量退出" +} +export const unableText = { + renewal: '无法续期', + handover: '无法移交', + remove: '无法直接退出', +} \ No newline at end of file diff --git a/src/frontend/devops-permission/src/utils/util.js b/src/frontend/devops-permission/src/utils/util.js new file mode 100644 index 000000000000..4876c6c4d0c6 --- /dev/null +++ b/src/frontend/devops-permission/src/utils/util.js @@ -0,0 +1,39 @@ +function prezero (num) { + num = Number(num) + + if (num < 10) { + return '0' + num + } + + return num +} + +export function convertTime (ms) { + if (!ms) return '--' + const time = new Date(ms) + + return `${time.getFullYear()}-${prezero(time.getMonth() + 1)}-${prezero(time.getDate())} ${prezero(time.getHours())}:${prezero(time.getMinutes())}:${prezero(time.getSeconds())}` +} + +export function deepEqual(obj1, obj2) { + if (obj1 === obj2) return true; + + if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) { + return false; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + const keys2Set = new Set(keys2); + + for (const key of keys1) { + if (!keys2Set.has(key) || !deepEqual(obj1[key], obj2[key])) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/src/frontend/devops-permission/src/views/applyPermission/apply-permission.vue b/src/frontend/devops-permission/src/views/applyPermission/apply-permission.vue index f486069a4478..2eb90e5ea24d 100644 --- a/src/frontend/devops-permission/src/views/applyPermission/apply-permission.vue +++ b/src/frontend/devops-permission/src/views/applyPermission/apply-permission.vue @@ -1,6 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/views/auth/auth-entry.vue b/src/frontend/devops-permission/src/views/auth/auth-entry.vue new file mode 100644 index 000000000000..5bb7b48833b6 --- /dev/null +++ b/src/frontend/devops-permission/src/views/auth/auth-entry.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/frontend/devops-permission/src/views/auth/oauth/oauth-card.vue b/src/frontend/devops-permission/src/views/auth/oauth/oauth-card.vue new file mode 100644 index 000000000000..44357764ac0f --- /dev/null +++ b/src/frontend/devops-permission/src/views/auth/oauth/oauth-card.vue @@ -0,0 +1,551 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/views/auth/oauth/oauth-home.vue b/src/frontend/devops-permission/src/views/auth/oauth/oauth-home.vue new file mode 100644 index 000000000000..b0ab244c8082 --- /dev/null +++ b/src/frontend/devops-permission/src/views/auth/oauth/oauth-home.vue @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/views/auth/permission/my-auth.vue b/src/frontend/devops-permission/src/views/auth/permission/my-auth.vue new file mode 100644 index 000000000000..7527a136145f --- /dev/null +++ b/src/frontend/devops-permission/src/views/auth/permission/my-auth.vue @@ -0,0 +1,851 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontend/devops-permission/src/views/my-permission/my-handover/handover-detail.vue b/src/frontend/devops-permission/src/views/my-permission/my-handover/handover-detail.vue new file mode 100644 index 000000000000..76ea7add44ba --- /dev/null +++ b/src/frontend/devops-permission/src/views/my-permission/my-handover/handover-detail.vue @@ -0,0 +1,487 @@ + + + + + diff --git a/src/frontend/devops-permission/src/views/my-permission/my-handover/index.vue b/src/frontend/devops-permission/src/views/my-permission/my-handover/index.vue new file mode 100644 index 000000000000..ecdcbeb92954 --- /dev/null +++ b/src/frontend/devops-permission/src/views/my-permission/my-handover/index.vue @@ -0,0 +1,641 @@ + + + + + + diff --git a/src/frontend/devops-permission/src/views/my-permission/my-permission.vue b/src/frontend/devops-permission/src/views/my-permission/my-permission.vue index 4633e95d3dd5..36748bb8706a 100644 --- a/src/frontend/devops-permission/src/views/my-permission/my-permission.vue +++ b/src/frontend/devops-permission/src/views/my-permission/my-permission.vue @@ -1,10 +1,1256 @@ - + + {{ selectedLength }} + +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + - + + + + + + diff --git a/src/frontend/devops-pipeline/package.json b/src/frontend/devops-pipeline/package.json index d20979498e63..9a942504b0af 100755 --- a/src/frontend/devops-pipeline/package.json +++ b/src/frontend/devops-pipeline/package.json @@ -5,7 +5,7 @@ "private": true, "author": "", "scripts": { - "dev": "nx exec -- webpack-cli serve --mode development --progress", + "dev": "webpack-cli serve --mode development --progress", "dll": "webpack --mode production --config webpack.dll.config.js", "public": "npm run dll && webpack --mode production", "public:dev": "cross-env NODE_ENV=dev npm run public --", @@ -24,6 +24,7 @@ "clipboard": "^1.7.1", "dayjs": "^1.11.2", "js-base64": "^3.7.2", + "js-cookie": "^3.0.5", "mavon-editor": "^2.10.4", "monaco-editor": "^0.40.0", "monaco-editor-webpack-plugin": "^7.1.0", diff --git a/src/frontend/devops-pipeline/src/components/AlertTips.vue b/src/frontend/devops-pipeline/src/components/AlertTips.vue new file mode 100644 index 000000000000..d95ac9a2894d --- /dev/null +++ b/src/frontend/devops-pipeline/src/components/AlertTips.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/src/frontend/devops-pipeline/src/components/AtomFormComponent/DevopsSelect/index.vue b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DevopsSelect/index.vue index 7cf15869e7a3..1366079ab73e 100644 --- a/src/frontend/devops-pipeline/src/components/AtomFormComponent/DevopsSelect/index.vue +++ b/src/frontend/devops-pipeline/src/components/AtomFormComponent/DevopsSelect/index.vue @@ -162,6 +162,23 @@ } this.displayName = keyArr.join(',') this.handleChange(this.name, params) + }, + value (newValue) { + if (this.isMultiple) { + if (this.displayName) { + this.getMultipleDisplayName(this.displayName, 'name') + } else { + this.getMultipleDisplayName(newValue) + } + } else { + if (this.isEnvVar(this.displayName) && this.displayName.trim() !== newValue) { + this.handleChange(this.name, this.displayName.trim()) + } else if (this.isEnvVar(newValue)) { + this.displayName = newValue + } else { + this.displayName = this.getDisplayName(newValue ?? this.displayName) + } + } } }, created () { @@ -269,22 +286,6 @@ this.isFocused = false this.$refs.inputArea && this.$refs.inputArea.blur() this.$emit('blur', null) - - if (this.isMultiple) { - if (this.displayName) { - this.getMultipleDisplayName(this.displayName, 'name') - } else { - this.getMultipleDisplayName(this.value) - } - } else { - if (this.isEnvVar(this.displayName)) { - this.handleChange(this.name, this.displayName.trim()) - } else if (this.isEnvVar(this.value)) { - this.displayName = this.value - } else { - this.displayName = this.getDisplayName(this.value ?? this.displayName) - } - } }, handleFocus (e) { @@ -436,98 +437,107 @@ diff --git a/src/frontend/devops-pipeline/src/components/AtomFormComponent/Parameter/index.vue b/src/frontend/devops-pipeline/src/components/AtomFormComponent/Parameter/index.vue index 89fe028a61ab..3d693fcab9ee 100755 --- a/src/frontend/devops-pipeline/src/components/AtomFormComponent/Parameter/index.vue +++ b/src/frontend/devops-pipeline/src/components/AtomFormComponent/Parameter/index.vue @@ -20,6 +20,7 @@ :list-type="parameter.keyListType" :url="parameter.keyUrl" :list="parameter.keyList" + :title="parameter.key" > = import mixins from '../mixins' import parameterInput from './parameterInput' + import { isObject } from '@/utils/util' export default { name: 'parameter', @@ -141,7 +144,12 @@ this.isLoading = true this.$ajax.get(url).then((res) => { - const data = res.data || [] + const data = res.data.map(i => { + return { + ...i, + value: isObject(i.value) ? JSON.stringify(i.value) : i.value + } + }) this.parameters = data this.setValue() }).catch(e => this.$showTips({ message: e.message, theme: 'error' })).finally(() => (this.isLoading = false)) @@ -169,6 +177,9 @@ if (Array.isArray(param.value)) { // 去掉空字符串, 空字符串无意义 param.value = param.value.filter(v => v !== '') } + if (isObject(param.value)) { + param.value = JSON.stringify(param.value) + } }) this.updateParameters() }, diff --git a/src/frontend/devops-pipeline/src/components/AtomFormComponent/SubParameter/index.vue b/src/frontend/devops-pipeline/src/components/AtomFormComponent/SubParameter/index.vue index e66eafa31e5a..a73391a705ba 100644 --- a/src/frontend/devops-pipeline/src/components/AtomFormComponent/SubParameter/index.vue +++ b/src/frontend/devops-pipeline/src/components/AtomFormComponent/SubParameter/index.vue @@ -40,6 +40,7 @@ @@ -56,6 +57,7 @@ + + diff --git a/src/frontend/devops-pipeline/src/components/PipelineTemplatePreview.vue b/src/frontend/devops-pipeline/src/components/PipelineTemplatePreview.vue index 65fdbb9b1e44..a447fafebf2a 100644 --- a/src/frontend/devops-pipeline/src/components/PipelineTemplatePreview.vue +++ b/src/frontend/devops-pipeline/src/components/PipelineTemplatePreview.vue @@ -35,6 +35,7 @@ :name="panel.name" >
- - - + + + + + + {{ $t('buildNoBaseline.templateManualResetRequired') }} + +
+

+ {{ tip }} +

+
+
{{ $t('buildNoBaseline.baselineValue') }} - {{ `${buildNo.buildNo} (${currentBuildNoType})` }} + + {{ `${buildNo.buildNo} (${currentBuildNoType})` }} + +

+ + {{ errors.first('buildNo') }} +

-
+
+ {{ $t('buildNoBaseline.strategy') }} + + {{ currentBuildNoType }} + +
+
{{ $t('buildNoBaseline.currentValue') }}

{{ errors.first('currentBuildNo') }} + + + {{ buildNo.buildNo }} +

@@ -100,12 +170,14 @@ import VuexInput from '@/components/atomFormField/VuexInput' import { allVersionKeyList, getVersionConfig } from '@/utils/pipelineConst' import { mapGetters } from 'vuex' + import Logo from '@/components/Logo' export default { components: { EnumInput, VuexInput, - FormField + FormField, + Logo }, props: { isPreview: { @@ -130,6 +202,22 @@ handleVersionChange: { type: Function, default: () => () => { } + }, + handleCheckChange: { + type: Function, + default: () => () => { } + }, + isInstance: Boolean, + isInitInstance: Boolean, + resetBuildNo: Boolean + }, + data () { + return { + baselineTooltipContent: { + allowHTML: true, + width: 610, + content: '#baseline-tooltip-content' + } } }, computed: { @@ -152,6 +240,21 @@ currentBuildNoType () { const buildNoItem = this.buildNoRules.find(item => item.value === this.buildNo.buildNoType) return buildNoItem ? buildNoItem.label : undefined + }, + buildNoBaselineTips () { + return Array(7).fill(0).map((_, i) => this.$t(`buildNoBaseline.tips${i + 1}`)) + }, + formType () { + return this.isTemplateEdit ? 'vertical' : 'inline' + }, + isLockedNo () { + return this.buildNo.buildNoType !== 'CONSISTENT' + }, + isTemplateEdit () { + return !this.isPreview && !this.isInstance + }, + isPreviewAndLockedNo () { + return (this.isLockedNo && this.isPreview) || this.disabled } } } @@ -164,6 +267,8 @@ grid-gap: 10px; .pipeline-execute-version-label { + display: flex; + align-items: center; font-size: 12px; font-weight: 700; @@ -171,6 +276,11 @@ font-weight: normal; color: #979ba5; } + + .instance_reset { + font-weight: normal; + margin-left: 18px; + } } .execute-build-version { @@ -178,6 +288,7 @@ grid-template-columns: repeat(3, 1fr); grid-gap: 16px; width: 222px; + margin-right: 20px; .execute-build-version-input:not(:last-child) { position: relative; @@ -195,10 +306,18 @@ display: grid; grid-gap: 8px; width: fit-content; + .baseline-tips { + svg { + vertical-align: middle; + } + .baseline-tips-text { + font-size: 12px; + color: #979BA5; + } + } } - .preview-buildno{ - margin-left: 20px; - + .preview-buildno { + margin-left: 0; .preview-buildno-params { display: flex; @@ -210,7 +329,7 @@ .build-label, .build-value { font-size: 12px; - padding: 0 8px; + padding: 0 12px; border: 1px solid #dcdee5; cursor: not-allowed; height: 32px; @@ -222,6 +341,7 @@ } .build-value { + min-width: 153px; margin-right: 16px; border-left: none; border-radius: 0 2px 2px 0; @@ -232,12 +352,25 @@ position: relative; display: flex; - .is-danger{ + .is-danger { position: absolute; white-space: nowrap; top: 70%; left: 0; } + + .reset-build-no { + display: flex; + align-items: center; + color: #3A84FF; + svg { + margin: 0 8px 0 16px; + } + } + } + + .build-input { + margin-right: 16px; } } } diff --git a/src/frontend/devops-pipeline/src/components/atomFormField/AtomAceEditor/index.vue b/src/frontend/devops-pipeline/src/components/atomFormField/AtomAceEditor/index.vue index 797665e71e58..6bca104faed8 100755 --- a/src/frontend/devops-pipeline/src/components/atomFormField/AtomAceEditor/index.vue +++ b/src/frontend/devops-pipeline/src/components/atomFormField/AtomAceEditor/index.vue @@ -146,7 +146,6 @@ diff --git a/src/frontend/devops-pipeline/src/components/FileParamInput/index.vue b/src/frontend/devops-pipeline/src/components/atomFormField/FileParamInput/index.vue similarity index 98% rename from src/frontend/devops-pipeline/src/components/FileParamInput/index.vue rename to src/frontend/devops-pipeline/src/components/atomFormField/FileParamInput/index.vue index cac9022bf66f..748d58505d93 100644 --- a/src/frontend/devops-pipeline/src/components/FileParamInput/index.vue +++ b/src/frontend/devops-pipeline/src/components/atomFormField/FileParamInput/index.vue @@ -58,7 +58,7 @@