mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into wrap-pod-logs
This commit is contained in:
commit
31c7ae118d
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extensions": [
|
|
||||||
"pod-menu",
|
|
||||||
"node-menu",
|
|
||||||
"metrics-cluster-feature",
|
|
||||||
"kube-object-event-status"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
10
.eslintrc.js
10
.eslintrc.js
@ -12,11 +12,21 @@ module.exports = {
|
|||||||
"**/static/**/*",
|
"**/static/**/*",
|
||||||
"**/site/**/*",
|
"**/site/**/*",
|
||||||
"extensions/*/*.tgz",
|
"extensions/*/*.tgz",
|
||||||
|
"build/webpack/**/*",
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
version: packageJson.devDependencies.react || "detect",
|
version: packageJson.devDependencies.react || "detect",
|
||||||
},
|
},
|
||||||
|
"import/parsers": {
|
||||||
|
"@typescript-eslint/parser": [".ts", ".tsx"],
|
||||||
|
},
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"alwaysTryTypes": true,
|
||||||
|
"project": "./tsconfig.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
36
.github/workflows/add-to-project-board.yaml
vendored
36
.github/workflows/add-to-project-board.yaml
vendored
@ -13,21 +13,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Add Card to Project(s)
|
name: Add Card to Project(s)
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Get Event Type
|
- name: Get Event Type
|
||||||
run: echo $GITHUB_EVENT_NAME
|
run: echo $GITHUB_EVENT_NAME
|
||||||
- name: Assign NEW issues to project 1
|
- name: Assign NEW issues to project 1
|
||||||
uses: ./.github/actions/add-card-to-project
|
uses: ./.github/actions/add-card-to-project
|
||||||
if: github.event_name == 'issues' && github.event.action == 'opened'
|
if: github.event_name == 'issues' && github.event.action == 'opened'
|
||||||
with:
|
with:
|
||||||
project: 'https://github.com/orgs/lensapp/projects/1'
|
project: "https://github.com/orgs/lensapp/projects/1"
|
||||||
column_name: 'Backlog'
|
column_name: "Backlog"
|
||||||
card_position: 'bottom'
|
card_position: "bottom"
|
||||||
- name: Assign NEW pull requests to project 1
|
- name: Assign NEW pull requests to project 1
|
||||||
uses: ./.github/actions/add-card-to-project
|
uses: ./.github/actions/add-card-to-project
|
||||||
if: github.event_name == 'pull_request_target' && github.event.action == 'opened'
|
if: github.event_name == 'pull_request_target' && github.event.action == 'opened'
|
||||||
with:
|
with:
|
||||||
project: 'https://github.com/orgs/lensapp/projects/1'
|
project: "https://github.com/orgs/lensapp/projects/1"
|
||||||
column_name: 'PRs'
|
column_name: "PRs"
|
||||||
card_position: 'bottom'
|
card_position: "bottom"
|
||||||
|
|||||||
35
.github/workflows/bump-master-version.yaml
vendored
Normal file
35
.github/workflows/bump-master-version.yaml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Bump Version on master
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Release from lens
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
npm i --location=global semver
|
||||||
|
- name: Bump version to first alpha of next minor version
|
||||||
|
run: |
|
||||||
|
NEW_VERSION=$(cat package.json | jq .version --raw-output| xargs semver -i preminor --preid alpha)
|
||||||
|
cat package.json | jq --arg new_version "$NEW_VERSION" '.version = $new_version' > new-package.json
|
||||||
|
mv new-package.json package.json
|
||||||
|
- uses: peter-evans/create-pull-request@v4
|
||||||
|
with:
|
||||||
|
add-paths: package.json
|
||||||
|
commit-message: Update package.json version to next preminor because of recent release
|
||||||
|
signoff: true
|
||||||
|
delete-branch: true
|
||||||
|
title: Update version to next preminor
|
||||||
|
labels: chore
|
||||||
4
.github/workflows/check-docs.yml
vendored
4
.github/workflows/check-docs.yml
vendored
@ -12,12 +12,12 @@ jobs:
|
|||||||
node-version: [16.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
|||||||
58
.github/workflows/codeql-analysis.yml
vendored
58
.github/workflows/codeql-analysis.yml
vendored
@ -13,12 +13,12 @@ name: "CodeQL"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '41 3 * * 2'
|
- cron: "41 3 * * 2"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
@ -32,40 +32,40 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: [ 'javascript' ]
|
language: ["javascript"]
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
# Learn more:
|
# Learn more:
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
# and modify them (or add more) to build your code if your project
|
# and modify them (or add more) to build your code if your project
|
||||||
# uses a compiled language
|
# uses a compiled language
|
||||||
|
|
||||||
#- run: |
|
#- run: |
|
||||||
# make bootstrap
|
# make bootstrap
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v1
|
||||||
|
|||||||
4
.github/workflows/electronegativity.yml
vendored
4
.github/workflows/electronegativity.yml
vendored
@ -10,9 +10,9 @@ jobs:
|
|||||||
build_job:
|
build_job:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "16"
|
||||||
|
|
||||||
|
|||||||
28
.github/workflows/license-header.yml
vendored
28
.github/workflows/license-header.yml
vendored
@ -11,18 +11,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Golang
|
- name: Set up Golang
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.18.0'
|
go-version: "^1.18.0"
|
||||||
- name: Install addlicense
|
- name: Install addlicense
|
||||||
run: |
|
run: |
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
go install github.com/google/addlicense@v1.0.0
|
go install github.com/google/addlicense@v1.0.0
|
||||||
- name: Check license headers
|
- name: Check license headers
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
|
|
||||||
addlicense -check -l mit -c "OpenLens Authors" src/**/*.?css
|
addlicense -check -l mit -c "OpenLens Authors" src/**/*.?css
|
||||||
|
|||||||
4
.github/workflows/linter.yml
vendored
4
.github/workflows/linter.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
|||||||
node-version: [16.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
|||||||
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -23,12 +23,12 @@ jobs:
|
|||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ jobs:
|
|||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ jobs:
|
|||||||
- name: Get the release version
|
- name: Get the release version
|
||||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||||
id: get_version
|
id: get_version
|
||||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: mkdocs deploy new release
|
- name: mkdocs deploy new release
|
||||||
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
if: contains(github.ref, 'refs/tags/v') && !github.event.release.prerelease
|
||||||
|
|||||||
14
.github/workflows/mkdocs-delete-version.yml
vendored
14
.github/workflows/mkdocs-delete-version.yml
vendored
@ -13,26 +13,24 @@ jobs:
|
|||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: git config
|
- name: git config
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
|
|
||||||
- name: mkdocs delete version
|
- name: mkdocs delete version
|
||||||
run: |
|
run: |
|
||||||
mike delete --push ${{ github.event.inputs.version }}
|
mike delete --push ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
|
|||||||
12
.github/workflows/mkdocs-manual.yml
vendored
12
.github/workflows/mkdocs-manual.yml
vendored
@ -25,13 +25,13 @@ jobs:
|
|||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Version from lens
|
- name: Checkout Version from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: "${{ github.event.inputs.version }}"
|
ref: "${{ github.event.inputs.version }}"
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ jobs:
|
|||||||
yarn typedocs-extensions-api
|
yarn typedocs-extensions-api
|
||||||
|
|
||||||
- name: Checkout master branch from lens
|
- name: Checkout master branch from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: "master"
|
path: "master"
|
||||||
ref: "master"
|
ref: "master"
|
||||||
@ -53,9 +53,9 @@ jobs:
|
|||||||
rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting
|
rm -fr ./docs/clusters ./docs/contributing ./docs/faq ./docs/getting-started ./docs/helm ./docs/support ./docs/supporting
|
||||||
sed -i '/Protocol Handlers/d' ./mkdocs.yml
|
sed -i '/Protocol Handlers/d' ./mkdocs.yml
|
||||||
sed -i '/IPC/d' ./mkdocs.yml
|
sed -i '/IPC/d' ./mkdocs.yml
|
||||||
sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/extensions/get-started/your-first-extension.md
|
sed -i 's#../../clusters/adding-clusters.md#https://docs.k8slens.dev/getting-started/add-cluster/#g' ./docs/extensions/get-started/your-first-extension.md
|
||||||
sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev/latest/clusters/adding-clusters/#g' ./docs/README.md
|
sed -i 's#clusters/adding-clusters.md#https://docs.k8slens.dev//getting-started/adding-clusters/#g' ./docs/README.md
|
||||||
sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/latest/contributing/#g' ./docs/extensions/guides/generator.md
|
sed -i 's#../../contributing/README.md#https://docs.k8slens.dev/contributing/#g' ./docs/extensions/guides/generator.md
|
||||||
|
|
||||||
- name: git config
|
- name: git config
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
14
.github/workflows/mkdocs-set-default-version.yml
vendored
14
.github/workflows/mkdocs-set-default-version.yml
vendored
@ -13,26 +13,24 @@ jobs:
|
|||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
pip install git+https://${{ secrets.GH_TOKEN }}@github.com/lensapp/mkdocs-material-insiders.git
|
||||||
pip install mike
|
pip install mike
|
||||||
|
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: git config
|
- name: git config
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
|
|
||||||
|
|
||||||
- name: mkdocs update default version
|
- name: mkdocs update default version
|
||||||
run: |
|
run: |
|
||||||
mike set-default --push ${{ github.event.inputs.version }}
|
mike set-default --push ${{ github.event.inputs.version }}
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/publish-master-npm.yml
vendored
4
.github/workflows/publish-master-npm.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
|||||||
node-version: [16.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release
|
- name: Checkout Release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/publish-release-npm.yml
vendored
4
.github/workflows/publish-release-npm.yml
vendored
@ -12,12 +12,12 @@ jobs:
|
|||||||
node-version: [16.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release
|
- name: Checkout Release
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
|||||||
16
.github/workflows/release-drafter.yml
vendored
16
.github/workflows/release-drafter.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Release Drafter
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# branches to consider in the event; optional, defaults to all
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update_release_draft:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
||||||
30
.github/workflows/release.yml
vendored
Normal file
30
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Release Open Lens
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- release/v*.*
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release') }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Release from lens
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: butlerlogic/action-autotag@stable
|
||||||
|
id: tagger
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
tag_prefix: "v"
|
||||||
|
- uses: ncipollo/release-action@v1
|
||||||
|
if: ${{ steps.tagger.outputs.tagname != '' }}
|
||||||
|
with:
|
||||||
|
name: ${{ steps.tagger.outputs.tagname }}
|
||||||
|
commit: master
|
||||||
|
tag: ${{ steps.tagger.outputs.tagname }}
|
||||||
|
body: ${{ github.event.pull_request.body }}
|
||||||
14
.github/workflows/require-milestone.yml
vendored
Normal file
14
.github/workflows/require-milestone.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: Require Milestone
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
jobs:
|
||||||
|
milestone:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Require Milestone
|
||||||
|
run: |
|
||||||
|
exit $(gh pr view ${{ github.event.pull_request.number }} --json milestone | jq 'if .milestone == null then 1 else 0 end')
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
37
.github/workflows/test.yml
vendored
37
.github/workflows/test.yml
vendored
@ -7,17 +7,18 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
name: Test
|
name: ${{ matrix.type }} tests on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, macos-11, windows-2019]
|
os: [ubuntu-20.04, macos-11, windows-2019]
|
||||||
|
type: [unit, smoke]
|
||||||
node-version: [16.x]
|
node-version: [16.x]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Release from lens
|
- name: Checkout Release from lens
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@ -27,15 +28,16 @@ jobs:
|
|||||||
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
|
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Using Node.js ${{ matrix.node-version }}
|
- name: Using Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
shell: bash
|
||||||
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
@ -51,25 +53,16 @@ jobs:
|
|||||||
retry_on: error
|
retry_on: error
|
||||||
command: make node_modules
|
command: make node_modules
|
||||||
|
|
||||||
- run: make build-npm
|
|
||||||
name: Generate npm package
|
|
||||||
|
|
||||||
- uses: nick-fields/retry@v2
|
|
||||||
name: Build bundled extensions
|
|
||||||
with:
|
|
||||||
timeout_minutes: 15
|
|
||||||
max_attempts: 3
|
|
||||||
retry_on: error
|
|
||||||
command: make -j2 build-extensions
|
|
||||||
|
|
||||||
- run: make test
|
- run: make test
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
if: ${{ matrix.type == 'unit' }}
|
||||||
|
|
||||||
- run: make test-extensions
|
- run: make test-extensions
|
||||||
name: Run In-tree Extension tests
|
name: Run In-tree Extension tests
|
||||||
|
if: ${{ matrix.type == 'unit' }}
|
||||||
|
|
||||||
- run: make ci-validate-dev
|
- run: make ci-validate-dev
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'dependencies')
|
if: ${{ contains(github.event.pull_request.labels.*.name, 'dependencies') && matrix.type == 'unit' }}
|
||||||
name: Validate dev mode will work
|
name: Validate dev mode will work
|
||||||
|
|
||||||
- name: Install integration test dependencies
|
- name: Install integration test dependencies
|
||||||
@ -77,22 +70,22 @@ jobs:
|
|||||||
uses: medyagh/setup-minikube@master
|
uses: medyagh/setup-minikube@master
|
||||||
with:
|
with:
|
||||||
minikube-version: latest
|
minikube-version: latest
|
||||||
if: runner.os == 'Linux'
|
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
|
||||||
|
|
||||||
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration
|
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' make integration
|
||||||
name: Run Linux integration tests
|
name: Run Linux integration tests
|
||||||
if: runner.os == 'Linux'
|
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
|
||||||
|
|
||||||
- run: make integration
|
- run: make integration
|
||||||
name: Run macOS integration tests
|
name: Run macOS integration tests
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --arm64"
|
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --arm64"
|
||||||
if: runner.os == 'macOS'
|
if: ${{ runner.os == 'macOS' && matrix.type == 'smoke' }}
|
||||||
|
|
||||||
- run: make integration
|
- run: make integration
|
||||||
name: Run Windows integration tests
|
name: Run Windows integration tests
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --ia32"
|
ELECTRON_BUILDER_EXTRA_ARGS: "--x64 --ia32"
|
||||||
if: runner.os == 'Windows'
|
if: ${{ runner.os == 'Windows' && matrix.type == 'smoke' }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ types/extension-renderer-api.d.ts
|
|||||||
extensions/*/dist
|
extensions/*/dist
|
||||||
docs/extensions/api
|
docs/extensions/api
|
||||||
site/
|
site/
|
||||||
|
build/webpack/
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# Contributing to Lens
|
# Contributing to Lens
|
||||||
|
|
||||||
See [Contributing to Lens](https://docs.k8slens.dev/latest/contributing/) documentation.
|
See [Contributing to Lens](https://docs.k8slens.dev/contributing/) documentation.
|
||||||
|
|||||||
9
Makefile
9
Makefile
@ -44,7 +44,7 @@ tag-release:
|
|||||||
scripts/tag-release.sh $(CMD_ARGS)
|
scripts/tag-release.sh $(CMD_ARGS)
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: binaries/client
|
test: node_modules binaries/client
|
||||||
yarn run jest $(or $(CMD_ARGS), "src")
|
yarn run jest $(or $(CMD_ARGS), "src")
|
||||||
|
|
||||||
.PHONY: integration
|
.PHONY: integration
|
||||||
@ -53,7 +53,6 @@ integration: build
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: node_modules binaries/client
|
build: node_modules binaries/client
|
||||||
yarn run npm:fix-build-version
|
|
||||||
$(MAKE) build-extensions -B
|
$(MAKE) build-extensions -B
|
||||||
yarn run build:tray-icons
|
yarn run build:tray-icons
|
||||||
yarn run compile
|
yarn run compile
|
||||||
@ -63,10 +62,6 @@ ifeq "$(DETECTED_OS)" "Windows"
|
|||||||
endif
|
endif
|
||||||
yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
yarn run electron-builder --publish onTag $(ELECTRON_BUILDER_EXTRA_ARGS)
|
||||||
|
|
||||||
.PHONY: update-extension-locks
|
|
||||||
update-extension-locks:
|
|
||||||
$(foreach dir, $(extensions), (cd $(dir) && rm package-lock.json && ../../node_modules/.bin/npm install --package-lock-only);)
|
|
||||||
|
|
||||||
.NOTPARALLEL: $(extension_node_modules)
|
.NOTPARALLEL: $(extension_node_modules)
|
||||||
$(extension_node_modules): node_modules
|
$(extension_node_modules): node_modules
|
||||||
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save
|
cd $(@:/node_modules=) && ../../node_modules/.bin/npm install --no-audit --no-fund --no-save
|
||||||
@ -92,7 +87,7 @@ src/extensions/npm/extensions/dist: src/extensions/npm/extensions/node_modules
|
|||||||
yarn compile:extension-types
|
yarn compile:extension-types
|
||||||
|
|
||||||
src/extensions/npm/extensions/node_modules: src/extensions/npm/extensions/package.json
|
src/extensions/npm/extensions/node_modules: src/extensions/npm/extensions/package.json
|
||||||
cd src/extensions/npm/extensions/ && ../../../../node_modules/.bin/npm install --no-audit --no-fund
|
cd src/extensions/npm/extensions/ && ../../../../node_modules/.bin/npm install --no-audit --no-fund --no-save
|
||||||
|
|
||||||
.PHONY: build-npm
|
.PHONY: build-npm
|
||||||
build-npm: build-extension-types src/extensions/npm/extensions/__mocks__
|
build-npm: build-extension-types src/extensions/npm/extensions/__mocks__
|
||||||
|
|||||||
10
README.md
10
README.md
@ -19,12 +19,16 @@ Lens IDE a standalone application for MacOS, Windows and Linux operating systems
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
See [Getting Started](https://docs.k8slens.dev/main/getting-started/install-lens/) page.
|
See [Getting Started](https://docs.k8slens.dev/getting-started/install-lens/) page.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
See [Development](https://docs.k8slens.dev/latest/contributing/development/) page.
|
See [Development](https://docs.k8slens.dev/contributing/development/) page.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See [Contributing](https://docs.k8slens.dev/latest/contributing/) page.
|
See [Contributing](https://docs.k8slens.dev/contributing/) page.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [License](LICENSE).
|
||||||
|
|||||||
19
RELEASE_GUIDE.md
Normal file
19
RELEASE_GUIDE.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Release Guide
|
||||||
|
|
||||||
|
Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`.
|
||||||
|
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relavent commits from master.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- `yarn`
|
||||||
|
- Running `yarn` (to install all dependencies)
|
||||||
|
- `gh` (Github's CLI) with a version at least 2.15.0
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. If you are making a minor or major release (or prereleases for one) make sure you are on the `master` branch.
|
||||||
|
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
|
||||||
|
1. Run `yarn create-release-pr <release-type>`. If you are making a subsequent prerelease release, provide the `--check-commits` flag.
|
||||||
|
1. If you are checking the commits, type `y<ENTER>` to pick a commit, and `n<ENTER>` to skip it. You will want to skip the commits that were part of previous prerelease releases.
|
||||||
|
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
|
||||||
|
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
|
||||||
@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mock the global window variable
|
|
||||||
*/
|
|
||||||
export function mockWindow() {
|
|
||||||
Object.defineProperty(window, "requestIdleCallback", {
|
|
||||||
writable: true,
|
|
||||||
value: jest.fn().mockImplementation(callback => callback()),
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window, "cancelIdleCallback", {
|
|
||||||
writable: true,
|
|
||||||
value: jest.fn(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -8,18 +8,30 @@ import { open } from "fs/promises";
|
|||||||
import type { WriteStream } from "fs-extra";
|
import type { WriteStream } from "fs-extra";
|
||||||
import { constants, ensureDir, unlink } from "fs-extra";
|
import { constants, ensureDir, unlink } from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fetch from "node-fetch";
|
import type * as FetchModule from "node-fetch";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
import { pipeline as _pipeline, Transform, Writable } from "stream";
|
||||||
import type { SingleBar } from "cli-progress";
|
import type { SingleBar } from "cli-progress";
|
||||||
import { MultiBar } from "cli-progress";
|
import { MultiBar } from "cli-progress";
|
||||||
import { extract } from "tar-stream";
|
import { extract } from "tar-stream";
|
||||||
import gunzip from "gunzip-maybe";
|
import gunzip from "gunzip-maybe";
|
||||||
import { getBinaryName, normalizedPlatform } from "../src/common/vars";
|
import { isErrnoException, setTimeoutFor } from "../src/common/utils";
|
||||||
import { isErrnoException } from "../src/common/utils";
|
import AbortController from "abort-controller";
|
||||||
|
|
||||||
|
type Response = FetchModule.Response;
|
||||||
|
type RequestInfo = FetchModule.RequestInfo;
|
||||||
|
type RequestInit = FetchModule.RequestInit;
|
||||||
|
|
||||||
const pipeline = promisify(_pipeline);
|
const pipeline = promisify(_pipeline);
|
||||||
|
|
||||||
|
const getBinaryName = (binaryName: string, { forPlatform }: { forPlatform : string }) => {
|
||||||
|
if (forPlatform === "windows") {
|
||||||
|
return `${binaryName}.exe`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return binaryName;
|
||||||
|
};
|
||||||
|
|
||||||
interface BinaryDownloaderArgs {
|
interface BinaryDownloaderArgs {
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
readonly platform: SupportedPlatform;
|
readonly platform: SupportedPlatform;
|
||||||
@ -29,6 +41,10 @@ interface BinaryDownloaderArgs {
|
|||||||
readonly baseDir: string;
|
readonly baseDir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface BinaryDownloaderDependencies {
|
||||||
|
fetch: (url: RequestInfo, init?: RequestInit) => Promise<Response>;
|
||||||
|
}
|
||||||
|
|
||||||
abstract class BinaryDownloader {
|
abstract class BinaryDownloader {
|
||||||
protected abstract readonly url: string;
|
protected abstract readonly url: string;
|
||||||
protected readonly bar: SingleBar;
|
protected readonly bar: SingleBar;
|
||||||
@ -38,7 +54,7 @@ abstract class BinaryDownloader {
|
|||||||
return [file];
|
return [file];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
|
constructor(protected readonly dependencies: BinaryDownloaderDependencies, public readonly args: BinaryDownloaderArgs, multiBar: MultiBar) {
|
||||||
this.bar = multiBar.create(1, 0, args);
|
this.bar = multiBar.create(1, 0, args);
|
||||||
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
|
this.target = path.join(args.baseDir, args.platform, args.fileArch, args.binaryName);
|
||||||
}
|
}
|
||||||
@ -49,8 +65,10 @@ abstract class BinaryDownloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const stream = await fetch(this.url, {
|
|
||||||
timeout: 15 * 60 * 1000, // 15min
|
setTimeoutFor(controller, 15 * 60 * 1000);
|
||||||
|
|
||||||
|
const stream = await this.dependencies.fetch(this.url, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
const total = Number(stream.headers.get("content-length"));
|
const total = Number(stream.headers.get("content-length"));
|
||||||
@ -72,6 +90,10 @@ abstract class BinaryDownloader {
|
|||||||
*/
|
*/
|
||||||
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
const handle = fileHandle = await open(this.target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
||||||
|
|
||||||
|
if (!stream.body) {
|
||||||
|
throw new Error("no body on stream");
|
||||||
|
}
|
||||||
|
|
||||||
await pipeline(
|
await pipeline(
|
||||||
stream.body,
|
stream.body,
|
||||||
new Transform({
|
new Transform({
|
||||||
@ -108,10 +130,10 @@ abstract class BinaryDownloader {
|
|||||||
class LensK8sProxyDownloader extends BinaryDownloader {
|
class LensK8sProxyDownloader extends BinaryDownloader {
|
||||||
protected readonly url: string;
|
protected readonly url: string;
|
||||||
|
|
||||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
|
const binaryName = getBinaryName("lens-k8s-proxy", { forPlatform: args.platform });
|
||||||
|
|
||||||
super({ ...args, binaryName }, bar);
|
super(deps, { ...args, binaryName }, bar);
|
||||||
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
|
this.url = `https://github.com/lensapp/lens-k8s-proxy/releases/download/v${args.version}/lens-k8s-proxy-${args.platform}-${args.downloadArch}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,10 +141,10 @@ class LensK8sProxyDownloader extends BinaryDownloader {
|
|||||||
class KubectlDownloader extends BinaryDownloader {
|
class KubectlDownloader extends BinaryDownloader {
|
||||||
protected readonly url: string;
|
protected readonly url: string;
|
||||||
|
|
||||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
|
const binaryName = getBinaryName("kubectl", { forPlatform: args.platform });
|
||||||
|
|
||||||
super({ ...args, binaryName }, bar);
|
super(deps, { ...args, binaryName }, bar);
|
||||||
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
|
this.url = `https://storage.googleapis.com/kubernetes-release/release/v${args.version}/bin/${args.platform}/${args.downloadArch}/${binaryName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,10 +152,10 @@ class KubectlDownloader extends BinaryDownloader {
|
|||||||
class HelmDownloader extends BinaryDownloader {
|
class HelmDownloader extends BinaryDownloader {
|
||||||
protected readonly url: string;
|
protected readonly url: string;
|
||||||
|
|
||||||
constructor(args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
constructor(deps: BinaryDownloaderDependencies, args: Omit<BinaryDownloaderArgs, "binaryName">, bar: MultiBar) {
|
||||||
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
|
const binaryName = getBinaryName("helm", { forPlatform: args.platform });
|
||||||
|
|
||||||
super({ ...args, binaryName }, bar);
|
super(deps, { ...args, binaryName }, bar);
|
||||||
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
|
this.url = `https://get.helm.sh/helm-v${args.version}-${args.platform}-${args.downloadArch}.tar.gz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +182,24 @@ class HelmDownloader extends BinaryDownloader {
|
|||||||
|
|
||||||
type SupportedPlatform = "darwin" | "linux" | "windows";
|
type SupportedPlatform = "darwin" | "linux" | "windows";
|
||||||
|
|
||||||
|
const importFetchModule = new Function('return import("node-fetch")') as () => Promise<typeof FetchModule>;
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
const deps: BinaryDownloaderDependencies = {
|
||||||
|
fetch: (await importFetchModule()).default,
|
||||||
|
};
|
||||||
|
const normalizedPlatform = (() => {
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin":
|
||||||
|
return "darwin";
|
||||||
|
case "linux":
|
||||||
|
return "linux";
|
||||||
|
case "win32":
|
||||||
|
return "windows";
|
||||||
|
default:
|
||||||
|
throw new Error(`platform=${process.platform} is unsupported`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
const multiBar = new MultiBar({
|
const multiBar = new MultiBar({
|
||||||
align: "left",
|
align: "left",
|
||||||
clearOnComplete: false,
|
clearOnComplete: false,
|
||||||
@ -171,21 +210,21 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
const baseDir = path.join(__dirname, "..", "binaries", "client");
|
||||||
const downloaders: BinaryDownloader[] = [
|
const downloaders: BinaryDownloader[] = [
|
||||||
new LensK8sProxyDownloader({
|
new LensK8sProxyDownloader(deps, {
|
||||||
version: packageInfo.config.k8sProxyVersion,
|
version: packageInfo.config.k8sProxyVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "amd64",
|
downloadArch: "amd64",
|
||||||
fileArch: "x64",
|
fileArch: "x64",
|
||||||
baseDir,
|
baseDir,
|
||||||
}, multiBar),
|
}, multiBar),
|
||||||
new KubectlDownloader({
|
new KubectlDownloader(deps, {
|
||||||
version: packageInfo.config.bundledKubectlVersion,
|
version: packageInfo.config.bundledKubectlVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "amd64",
|
downloadArch: "amd64",
|
||||||
fileArch: "x64",
|
fileArch: "x64",
|
||||||
baseDir,
|
baseDir,
|
||||||
}, multiBar),
|
}, multiBar),
|
||||||
new HelmDownloader({
|
new HelmDownloader(deps, {
|
||||||
version: packageInfo.config.bundledHelmVersion,
|
version: packageInfo.config.bundledHelmVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "amd64",
|
downloadArch: "amd64",
|
||||||
@ -194,23 +233,23 @@ async function main() {
|
|||||||
}, multiBar),
|
}, multiBar),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (normalizedPlatform === "darwin") {
|
if (normalizedPlatform !== "windows") {
|
||||||
downloaders.push(
|
downloaders.push(
|
||||||
new LensK8sProxyDownloader({
|
new LensK8sProxyDownloader(deps, {
|
||||||
version: packageInfo.config.k8sProxyVersion,
|
version: packageInfo.config.k8sProxyVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "arm64",
|
downloadArch: "arm64",
|
||||||
fileArch: "arm64",
|
fileArch: "arm64",
|
||||||
baseDir,
|
baseDir,
|
||||||
}, multiBar),
|
}, multiBar),
|
||||||
new KubectlDownloader({
|
new KubectlDownloader(deps, {
|
||||||
version: packageInfo.config.bundledKubectlVersion,
|
version: packageInfo.config.bundledKubectlVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "arm64",
|
downloadArch: "arm64",
|
||||||
fileArch: "arm64",
|
fileArch: "arm64",
|
||||||
baseDir,
|
baseDir,
|
||||||
}, multiBar),
|
}, multiBar),
|
||||||
new HelmDownloader({
|
new HelmDownloader(deps, {
|
||||||
version: packageInfo.config.bundledHelmVersion,
|
version: packageInfo.config.bundledHelmVersion,
|
||||||
platform: normalizedPlatform,
|
platform: normalizedPlatform,
|
||||||
downloadArch: "arm64",
|
downloadArch: "arm64",
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
!macro customInit
|
!macro customInit
|
||||||
|
; Make sure all old extensions are removed
|
||||||
|
RMDir /r "$INSTDIR\resources\extensions"
|
||||||
|
|
||||||
; Workaround for installer handing when the app directory is removed manually
|
; Workaround for installer handing when the app directory is removed manually
|
||||||
${ifNot} ${FileExists} "$INSTDIR"
|
${ifNot} ${FileExists} "$INSTDIR"
|
||||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${UNINSTALL_APP_KEY}}"
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${UNINSTALL_APP_KEY}}"
|
||||||
|
|||||||
@ -22,5 +22,6 @@ exports.default = async function notarizing(context) {
|
|||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: process.env.APPLEID,
|
appleId: process.env.APPLEID,
|
||||||
appleIdPassword: process.env.APPLEIDPASS,
|
appleIdPassword: process.env.APPLEIDPASS,
|
||||||
|
ascProvider:process.env.ASCPROVIDER,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import * as fse from "fs-extra";
|
|
||||||
import * as path from "path";
|
|
||||||
import appInfo from "../package.json";
|
|
||||||
import semver from "semver";
|
|
||||||
import fastGlob from "fast-glob";
|
|
||||||
|
|
||||||
const packagePath = path.join(__dirname, "../package.json");
|
|
||||||
const versionInfo = semver.parse(appInfo.version);
|
|
||||||
const buildNumber = process.env.BUILD_NUMBER || Date.now().toString();
|
|
||||||
|
|
||||||
function getBuildChannel(): string {
|
|
||||||
const preRelease = versionInfo.prerelease?.[0];
|
|
||||||
|
|
||||||
switch (preRelease) {
|
|
||||||
case "alpha":
|
|
||||||
case "beta":
|
|
||||||
case "rc":
|
|
||||||
return preRelease;
|
|
||||||
case undefined:
|
|
||||||
case "latest":
|
|
||||||
return "latest"; // needed because electron-updater does not take build information into account when resolving if update is available
|
|
||||||
default:
|
|
||||||
throw new Error(`invalid pre-release ${preRelease}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeOutExtensionVersion(manifestPath: string) {
|
|
||||||
const extensionPackageJson = await fse.readJson(manifestPath);
|
|
||||||
|
|
||||||
extensionPackageJson.version = appInfo.version;
|
|
||||||
|
|
||||||
return fse.writeJson(manifestPath, extensionPackageJson, {
|
|
||||||
spaces: 2,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeOutNewVersions() {
|
|
||||||
await Promise.all([
|
|
||||||
fse.writeJson(packagePath, appInfo, {
|
|
||||||
spaces: 2,
|
|
||||||
}),
|
|
||||||
...(await fastGlob(["extensions/*/package.json"])).map(writeOutExtensionVersion),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
const prereleaseParts: string[] = [getBuildChannel()];
|
|
||||||
|
|
||||||
if (versionInfo.prerelease && versionInfo.prerelease.length > 1) {
|
|
||||||
prereleaseParts.push(versionInfo.prerelease[1].toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
prereleaseParts.push(buildNumber);
|
|
||||||
|
|
||||||
appInfo.version = `${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}-${prereleaseParts.join(".")}`;
|
|
||||||
|
|
||||||
writeOutNewVersions()
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -78,7 +78,7 @@ npm run dev
|
|||||||
You must restart Lens for the extension to load.
|
You must restart Lens for the extension to load.
|
||||||
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
After this initial restart, reload Lens and it will automatically pick up changes any time the extension rebuilds.
|
||||||
|
|
||||||
With Lens running, either connect to an existing cluster or create a new one - refer to the latest [Lens Documentation](https://docs.k8slens.dev/main/catalog/) for details on how to add a cluster in Lens IDE.
|
With Lens running, either connect to an existing cluster or create a new one - refer to the latest [Lens Documentation](https://docs.k8slens.dev/getting-started/add-cluster/) for details on how to add a cluster in Lens IDE.
|
||||||
You will see the "Hello World" page in the left-side cluster menu.
|
You will see the "Hello World" page in the left-side cluster menu.
|
||||||
|
|
||||||
## Develop the Extension
|
## Develop the Extension
|
||||||
|
|||||||
@ -46,14 +46,14 @@ Open `my-first-lens-ext/renderer.tsx` and change the value of `title` from `"Hel
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
clusterPageMenus = [
|
clusterPageMenus = [
|
||||||
{
|
{
|
||||||
target: { pageId: "hello" },
|
target: { pageId: "hello" },
|
||||||
title: "Hello Lens",
|
title: "Hello Lens",
|
||||||
components: {
|
components: {
|
||||||
Icon: ExampleIcon,
|
Icon: ExampleIcon,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
|
Reload Lens and you will see that the menu item text has changed to "Hello Lens".
|
||||||
@ -70,6 +70,6 @@ To debug your extension, please see our instructions on [Testing Extensions](../
|
|||||||
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
To dive deeper, consider looking at [Common Capabilities](../capabilities/common-capabilities.md), [Styling](../capabilities/styling.md), or [Extension Anatomy](anatomy.md).
|
||||||
|
|
||||||
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
|
If you find problems with the Lens Extension Generator, or have feature requests, you are welcome to raise an [issue](https://github.com/lensapp/generator-lens-ext/issues).
|
||||||
You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/latest/contributing).
|
You can find the latest Lens contribution guidelines [here](https://docs.k8slens.dev/contributing).
|
||||||
|
|
||||||
The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext).
|
The Generator source code is hosted at [GitHub](https://github.com/lensapp/generator-lens-ext).
|
||||||
|
|||||||
@ -771,7 +771,7 @@ Construct the table using the `Renderer.Component.Table` and related elements.
|
|||||||
|
|
||||||
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
|
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
|
||||||
The table is constructed using the `Renderer.Component.Table` and related elements.
|
The table is constructed using the `Renderer.Component.Table` and related elements.
|
||||||
See [Component documentation](https://docs.k8slens.dev/latest/extensions/api/modules/_renderer_api_components_/) for further details.
|
See [Component documentation](https://api-docs.k8slens.dev/latest/extensions/api/modules/Renderer.Component/) for further details.
|
||||||
|
|
||||||
### `kubeObjectStatusTexts`
|
### `kubeObjectStatusTexts`
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "6.1.1",
|
||||||
"description": "Adds kube object status from events",
|
"description": "Adds kube object status from events",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -35,12 +35,18 @@ export default class EventResourceStatusRendererExtension extends Renderer.LensE
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "Job",
|
kind: "Job",
|
||||||
apiVersions: ["batch/v1"],
|
apiVersions: [
|
||||||
|
"batch/v1",
|
||||||
|
"batch/v1beta1",
|
||||||
|
],
|
||||||
resolve: (job: Renderer.K8sApi.Job) => resolveStatus(job),
|
resolve: (job: Renderer.K8sApi.Job) => resolveStatus(job),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "CronJob",
|
kind: "CronJob",
|
||||||
apiVersions: ["batch/v1"],
|
apiVersions: [
|
||||||
|
"batch/v1",
|
||||||
|
"batch/v1beta1",
|
||||||
|
],
|
||||||
resolve: (cronJob: Renderer.K8sApi.CronJob) => resolveStatusForCronJobs(cronJob),
|
resolve: (cronJob: Renderer.K8sApi.CronJob) => resolveStatusForCronJobs(cronJob),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "6.1.0",
|
||||||
"description": "Lens metrics cluster feature",
|
"description": "Lens metrics cluster feature",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -190,7 +190,7 @@ export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<section style={{ display: "flex", flexDirection: "column", rowGap: "1.5rem" }}>
|
||||||
{ this.props.cluster.status.phase !== "connected" && (
|
{ this.props.cluster.status.phase !== "connected" && (
|
||||||
<section>
|
<section>
|
||||||
<p style={ { color: "var(--colorError)" } }>
|
<p style={ { color: "var(--colorError)" } }>
|
||||||
@ -270,7 +270,7 @@ export class MetricsSettings extends React.Component<MetricsSettingsProps> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "6.1.0",
|
||||||
"description": "Lens node menu",
|
"description": "Lens node menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "6.1.0",
|
||||||
"description": "Lens pod menu",
|
"description": "Lens pod menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -13,7 +13,8 @@ import type { ElectronApplication, Page } from "playwright";
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
|
||||||
describe("preferences page tests", () => {
|
describe("preferences page tests", () => {
|
||||||
let window: Page, cleanup: () => Promise<void>;
|
let window: Page;
|
||||||
|
let cleanup: undefined | (() => Promise<void>);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
let app: ElectronApplication;
|
let app: ElectronApplication;
|
||||||
@ -23,20 +24,21 @@ describe("preferences page tests", () => {
|
|||||||
|
|
||||||
await app.evaluate(async ({ app }) => {
|
await app.evaluate(async ({ app }) => {
|
||||||
await app.applicationMenu
|
await app.applicationMenu
|
||||||
.getMenuItemById(process.platform === "darwin" ? "root" : "file")
|
?.getMenuItemById(process.platform === "darwin" ? "mac" : "file")
|
||||||
.submenu.getMenuItemById("preferences")
|
?.submenu
|
||||||
.click();
|
?.getMenuItemById("navigate-to-preferences")
|
||||||
|
?.click();
|
||||||
});
|
});
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await cleanup();
|
await cleanup?.();
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
it('shows "preferences" and can navigate through the tabs', async () => {
|
it('shows "preferences" and can navigate through the tabs', async () => {
|
||||||
const pages = [
|
const pages = [
|
||||||
{
|
{
|
||||||
id: "application",
|
id: "app",
|
||||||
header: "Application",
|
header: "Application",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -50,8 +52,8 @@ describe("preferences page tests", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const { id, header } of pages) {
|
for (const { id, header } of pages) {
|
||||||
await window.click(`[data-testid=tab-link-for-${id}]`);
|
await window.click(`[data-preference-tab-link-test=${id}]`);
|
||||||
await window.waitForSelector(`[data-testid=${id}-header] >> text=${header}`);
|
await window.waitForSelector(`[data-preference-page-title-test] >> text=${header}`);
|
||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
|
|||||||
@ -14,11 +14,14 @@ import { minikubeReady } from "../helpers/minikube";
|
|||||||
import type { Frame, Page } from "playwright";
|
import type { Frame, Page } from "playwright";
|
||||||
import { groupBy, toPairs } from "lodash/fp";
|
import { groupBy, toPairs } from "lodash/fp";
|
||||||
import { pipeline } from "@ogre-tools/fp";
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { describeIf } from "../../src/test-utils/skippers";
|
||||||
|
|
||||||
const TEST_NAMESPACE = "integration-tests";
|
const TEST_NAMESPACE = "integration-tests";
|
||||||
|
|
||||||
utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
||||||
let window: Page, cleanup: () => Promise<void>, frame: Frame;
|
let window: Page;
|
||||||
|
let cleanup: undefined | (() => Promise<void>);
|
||||||
|
let frame: Frame;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ window, cleanup } = await utils.start());
|
({ window, cleanup } = await utils.start());
|
||||||
@ -28,7 +31,7 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
}, 10 * 60 * 1000);
|
}, 10 * 60 * 1000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await cleanup();
|
await cleanup?.();
|
||||||
}, 10 * 60 * 1000);
|
}, 10 * 60 * 1000);
|
||||||
|
|
||||||
it("shows cluster context menu in sidebar", async () => {
|
it("shows cluster context menu in sidebar", async () => {
|
||||||
@ -388,12 +391,6 @@ const scenarios = [
|
|||||||
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
sidebarItemTestId: "sidebar-item-link-for-service-accounts",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
expectedSelector: "h5.title",
|
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
|
||||||
sidebarItemTestId: "sidebar-item-link-for-roles",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
@ -403,7 +400,7 @@ const scenarios = [
|
|||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
sidebarItemTestId: "sidebar-item-link-for-roles",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -415,7 +412,7 @@ const scenarios = [
|
|||||||
{
|
{
|
||||||
expectedSelector: "h5.title",
|
expectedSelector: "h5.title",
|
||||||
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
parentSidebarItemTestId: "sidebar-item-link-for-user-management",
|
||||||
sidebarItemTestId: "sidebar-item-link-for-pod-security-policies",
|
sidebarItemTestId: "sidebar-item-link-for-role-bindings",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import type { ElectronApplication, Page } from "playwright";
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
|
|
||||||
describe("Lens command palette", () => {
|
describe("Lens command palette", () => {
|
||||||
let window: Page, cleanup: () => Promise<void>, app: ElectronApplication;
|
let window: Page;
|
||||||
|
let cleanup: undefined | (() => Promise<void>);
|
||||||
|
let app: ElectronApplication;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
({ window, cleanup, app } = await utils.start());
|
({ window, cleanup, app } = await utils.start());
|
||||||
@ -15,7 +17,7 @@ describe("Lens command palette", () => {
|
|||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await cleanup();
|
await cleanup?.();
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
describe("menu", () => {
|
describe("menu", () => {
|
||||||
@ -23,7 +25,7 @@ describe("Lens command palette", () => {
|
|||||||
await app.evaluate(async ({ app }) => {
|
await app.evaluate(async ({ app }) => {
|
||||||
await app.applicationMenu
|
await app.applicationMenu
|
||||||
?.getMenuItemById("view")
|
?.getMenuItemById("view")
|
||||||
?.submenu?.getMenuItemById("command-palette")
|
?.submenu?.getMenuItemById("open-command-palette")
|
||||||
?.click();
|
?.click();
|
||||||
});
|
});
|
||||||
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
await window.waitForSelector(".Select__option >> text=Hotbar: Switch");
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import * as uuid from "uuid";
|
|||||||
import type { ElectronApplication, Frame, Page } from "playwright";
|
import type { ElectronApplication, Frame, Page } from "playwright";
|
||||||
import { _electron as electron } from "playwright";
|
import { _electron as electron } from "playwright";
|
||||||
import { noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
|
import { disposer } from "../../src/common/utils";
|
||||||
|
|
||||||
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
"win32": "./dist/win-unpacked/OpenLens.exe",
|
"win32": "./dist/win-unpacked/OpenLens.exe",
|
||||||
@ -17,28 +18,47 @@ export const appPaths: Partial<Record<NodeJS.Platform, string>> = {
|
|||||||
"darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens",
|
"darwin": "./dist/mac/OpenLens.app/Contents/MacOS/OpenLens",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function itIf(condition: boolean) {
|
|
||||||
return condition ? it : it.skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function describeIf(condition: boolean) {
|
|
||||||
return condition ? describe : describe.skip;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
|
async function getMainWindow(app: ElectronApplication, timeout = 50_000): Promise<Page> {
|
||||||
const deadline = Date.now() + timeout;
|
return new Promise((resolve, reject) => {
|
||||||
|
const cleanup = disposer();
|
||||||
|
let stdoutBuf = "";
|
||||||
|
|
||||||
|
const onWindow = (page: Page) => {
|
||||||
|
console.log(`Page opened: ${page.url()}`);
|
||||||
|
|
||||||
for (; Date.now() < deadline;) {
|
|
||||||
for (const page of app.windows()) {
|
|
||||||
if (page.url().startsWith("http://localhost")) {
|
if (page.url().startsWith("http://localhost")) {
|
||||||
return page;
|
cleanup();
|
||||||
|
console.log(stdoutBuf);
|
||||||
|
resolve(page);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2_000));
|
app.on("window", onWindow);
|
||||||
}
|
cleanup.push(() => app.off("window", onWindow));
|
||||||
|
|
||||||
throw new Error(`Lens did not open the main window within ${timeout}ms`);
|
const onClose = () => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error("App has unnexpectedly closed"));
|
||||||
|
};
|
||||||
|
|
||||||
|
app.on("close", onClose);
|
||||||
|
cleanup.push(() => app.off("close", onClose));
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const stdout = app.process().stdout!;
|
||||||
|
const onData = (chunk: any) => stdoutBuf += chunk.toString();
|
||||||
|
|
||||||
|
stdout.on("data", onData);
|
||||||
|
cleanup.push(() => stdout.off("data", onData));
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
console.log(stdoutBuf);
|
||||||
|
reject(new Error(`Lens did not open the main window within ${timeout}ms`));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
cleanup.push(() => clearTimeout(timeoutId));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function attemptStart() {
|
async function attemptStart() {
|
||||||
@ -57,7 +77,7 @@ async function attemptStart() {
|
|||||||
...process.env,
|
...process.env,
|
||||||
},
|
},
|
||||||
timeout: 100_000,
|
timeout: 100_000,
|
||||||
} as Parameters<typeof electron["launch"]>[0]);
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const window = await getMainWindow(app);
|
const window = await getMainWindow(app);
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
prometheus+:: {
|
kubernetesControlPlane+:: {
|
||||||
serviceMonitorKubelet+: {
|
serviceMonitorKubelet+: {
|
||||||
spec+: {
|
spec+: {
|
||||||
endpoints: std.map(function(endpoint)
|
endpoints: std.map(function(endpoint)
|
||||||
|
|||||||
141
package.json
141
package.json
@ -3,7 +3,7 @@
|
|||||||
"productName": "OpenLens",
|
"productName": "OpenLens",
|
||||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||||
"homepage": "https://github.com/lensapp/lens",
|
"homepage": "https://github.com/lensapp/lens",
|
||||||
"version": "6.0.0",
|
"version": "6.3.0-alpha.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2022 OpenLens Authors",
|
"copyright": "© 2022 OpenLens Authors",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -26,7 +26,8 @@
|
|||||||
"compile:main": "yarn run webpack --config webpack/main.ts",
|
"compile:main": "yarn run webpack --config webpack/main.ts",
|
||||||
"compile:renderer": "yarn run webpack --config webpack/renderer.ts",
|
"compile:renderer": "yarn run webpack --config webpack/renderer.ts",
|
||||||
"compile:extension-types": "yarn run webpack --config webpack/extensions.ts",
|
"compile:extension-types": "yarn run webpack --config webpack/extensions.ts",
|
||||||
"npm:fix-build-version": "yarn run ts-node build/set_build_version.ts",
|
"compile:node-fetch": "yarn run webpack --config ./webpack/node-fetch.ts",
|
||||||
|
"postinstall": "yarn run compile:node-fetch",
|
||||||
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
|
"npm:fix-package-version": "yarn run ts-node build/set_npm_version.ts",
|
||||||
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
"build:linux": "yarn run compile && electron-builder --linux --dir",
|
||||||
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
"build:mac": "yarn run compile && electron-builder --mac --dir",
|
||||||
@ -52,11 +53,12 @@
|
|||||||
"create-release-pr": "node ./scripts/create-release-pr.mjs"
|
"create-release-pr": "node ./scripts/create-release-pr.mjs"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"k8sProxyVersion": "0.2.1",
|
"k8sProxyVersion": "0.3.0",
|
||||||
"bundledKubectlVersion": "1.23.3",
|
"bundledKubectlVersion": "1.23.3",
|
||||||
"bundledHelmVersion": "3.7.2",
|
"bundledHelmVersion": "3.7.2",
|
||||||
"sentryDsn": "",
|
"sentryDsn": "",
|
||||||
"contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:"
|
"contentSecurityPolicy": "script-src 'unsafe-eval' 'self'; frame-src http://*.localhost:*/; img-src * data:",
|
||||||
|
"welcomeRoute": "/welcome"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 <17"
|
"node": ">=16 <17"
|
||||||
@ -216,14 +218,14 @@
|
|||||||
"@astronautlabs/jsonpath": "^1.1.0",
|
"@astronautlabs/jsonpath": "^1.1.0",
|
||||||
"@hapi/call": "^9.0.0",
|
"@hapi/call": "^9.0.0",
|
||||||
"@hapi/subtext": "^7.0.4",
|
"@hapi/subtext": "^7.0.4",
|
||||||
"@kubernetes/client-node": "^0.17.0",
|
"@kubernetes/client-node": "^0.17.1",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
"@ogre-tools/fp": "10.1.0",
|
"@ogre-tools/fp": "^12.0.1",
|
||||||
"@ogre-tools/injectable": "10.1.0",
|
"@ogre-tools/injectable": "^12.0.1",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "10.1.0",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1",
|
||||||
"@ogre-tools/injectable-extension-for-mobx": "10.1.0",
|
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1",
|
||||||
"@ogre-tools/injectable-react": "10.1.0",
|
"@ogre-tools/injectable-react": "^12.0.1",
|
||||||
"@sentry/electron": "^3.0.7",
|
"@sentry/electron": "^3.0.8",
|
||||||
"@sentry/integrations": "^6.19.3",
|
"@sentry/integrations": "^6.19.3",
|
||||||
"@side/jest-runtime": "^1.0.1",
|
"@side/jest-runtime": "^1.0.1",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.18",
|
"@tanstack/react-virtual": "3.0.0-beta.18",
|
||||||
@ -246,43 +248,40 @@
|
|||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.16",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.7.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsdom": "^16.7.0",
|
"jsdom": "^16.7.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"mac-ca": "^1.0.6",
|
"marked": "^4.2.3",
|
||||||
"marked": "^4.1.0",
|
|
||||||
"md5-file": "^5.0.0",
|
"md5-file": "^5.0.0",
|
||||||
"mobx": "^6.6.2",
|
"mobx": "^6.7.0",
|
||||||
"mobx-observable-history": "^2.0.3",
|
"mobx-observable-history": "^2.0.3",
|
||||||
"mobx-react": "^7.5.3",
|
"mobx-react": "^7.6.0",
|
||||||
"mobx-utils": "^6.0.4",
|
"mobx-utils": "^6.0.4",
|
||||||
"mock-fs": "^5.1.4",
|
"mock-fs": "^5.2.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.37",
|
"moment-timezone": "^0.5.39",
|
||||||
"monaco-editor": "^0.29.1",
|
"monaco-editor": "^0.29.1",
|
||||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^3.3.0",
|
||||||
"node-pty": "0.10.1",
|
"node-pty": "0.10.1",
|
||||||
"npm": "^8.19.1",
|
"npm": "^8.19.3",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"path-to-regexp": "^6.2.0",
|
"path-to-regexp": "^6.2.0",
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-material-ui-carousel": "^2.3.11",
|
"react-material-ui-carousel": "^2.3.11",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.3.4",
|
||||||
"react-virtualized-auto-sizer": "^1.0.6",
|
"react-virtualized-auto-sizer": "^1.0.7",
|
||||||
"readable-stream": "^3.6.0",
|
"readable-stream": "^3.6.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-promise-native": "^1.0.9",
|
"request-promise-native": "^1.0.9",
|
||||||
"rfc6902": "^4.0.2",
|
"rfc6902": "^4.0.2",
|
||||||
"selfsigned": "^2.1.1",
|
"selfsigned": "^2.1.1",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.8",
|
||||||
"shell-env": "^3.0.1",
|
"tar": "^6.1.12",
|
||||||
"spdy": "^4.0.2",
|
|
||||||
"tar": "^6.1.11",
|
|
||||||
"tcp-port-used": "^1.0.2",
|
"tcp-port-used": "^1.0.2",
|
||||||
"tempy": "1.0.1",
|
"tempy": "1.0.1",
|
||||||
"typed-regex": "^0.0.8",
|
"typed-regex": "^0.0.8",
|
||||||
@ -290,9 +289,8 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"win-ca": "^3.5.0",
|
"win-ca": "^3.5.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-console-format": "^1.0.8",
|
|
||||||
"winston-transport-browserconsole": "^1.0.5",
|
"winston-transport-browserconsole": "^1.0.5",
|
||||||
"ws": "^8.8.1",
|
"ws": "^8.11.0",
|
||||||
"xterm-link-provider": "^1.3.1"
|
"xterm-link-provider": "^1.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -300,11 +298,11 @@
|
|||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
|
||||||
"@sentry/types": "^6.19.7",
|
"@sentry/types": "^6.19.7",
|
||||||
"@swc/cli": "^0.1.57",
|
"@swc/cli": "^0.1.57",
|
||||||
"@swc/core": "^1.2.249",
|
"@swc/core": "^1.3.19",
|
||||||
"@swc/jest": "^0.2.22",
|
"@swc/jest": "^0.2.23",
|
||||||
"@testing-library/dom": "^7.31.2",
|
"@testing-library/dom": "^7.31.2",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^12.1.5",
|
||||||
@ -316,7 +314,7 @@
|
|||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"@types/command-line-args": "^5.2.0",
|
"@types/command-line-args": "^5.2.0",
|
||||||
"@types/crypto-js": "^3.1.47",
|
"@types/crypto-js": "^3.1.47",
|
||||||
"@types/dompurify": "^2.3.4",
|
"@types/dompurify": "^2.4.0",
|
||||||
"@types/electron-devtools-installer": "^2.2.1",
|
"@types/electron-devtools-installer": "^2.2.1",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/glob-to-regexp": "^0.4.1",
|
"@types/glob-to-regexp": "^0.4.1",
|
||||||
@ -328,20 +326,19 @@
|
|||||||
"@types/jest": "^28.1.6",
|
"@types/jest": "^28.1.6",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/lodash": "^4.14.184",
|
"@types/lodash": "^4.14.190",
|
||||||
"@types/marked": "^4.0.7",
|
"@types/marked": "^4.0.7",
|
||||||
"@types/md5-file": "^4.0.2",
|
"@types/md5-file": "^4.0.2",
|
||||||
|
"@types/memorystream": "^0.3.0",
|
||||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^16.11.58",
|
"@types/node": "^16.18.2",
|
||||||
"@types/node-fetch": "^2.6.2",
|
|
||||||
"@types/npm": "^2.0.32",
|
|
||||||
"@types/proper-lockfile": "^4.1.2",
|
"@types/proper-lockfile": "^4.1.2",
|
||||||
"@types/randomcolor": "^0.5.6",
|
"@types/randomcolor": "^0.5.6",
|
||||||
"@types/react": "^17.0.45",
|
"@types/react": "^17.0.45",
|
||||||
"@types/react-beautiful-dnd": "^13.1.2",
|
"@types/react-beautiful-dnd": "^13.1.2",
|
||||||
"@types/react-dom": "^17.0.16",
|
"@types/react-dom": "^17.0.16",
|
||||||
"@types/react-router": "^5.1.18",
|
"@types/react-router": "^5.1.19",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.12",
|
||||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||||
@ -349,10 +346,9 @@
|
|||||||
"@types/readable-stream": "^2.3.13",
|
"@types/readable-stream": "^2.3.13",
|
||||||
"@types/request": "^2.48.7",
|
"@types/request": "^2.48.7",
|
||||||
"@types/request-promise-native": "^1.0.18",
|
"@types/request-promise-native": "^1.0.18",
|
||||||
"@types/semver": "^7.3.12",
|
"@types/semver": "^7.3.13",
|
||||||
"@types/sharp": "^0.30.5",
|
"@types/sharp": "^0.31.0",
|
||||||
"@types/spdy": "^3.4.5",
|
"@types/tar": "^6.1.3",
|
||||||
"@types/tar": "^4.0.5",
|
|
||||||
"@types/tar-stream": "^2.2.2",
|
"@types/tar-stream": "^2.2.2",
|
||||||
"@types/tcp-port-used": "^1.0.1",
|
"@types/tcp-port-used": "^1.0.1",
|
||||||
"@types/tempy": "^0.3.0",
|
"@types/tempy": "^0.3.0",
|
||||||
@ -363,31 +359,32 @@
|
|||||||
"@types/webpack-dev-server": "^4.7.2",
|
"@types/webpack-dev-server": "^4.7.2",
|
||||||
"@types/webpack-env": "^1.18.0",
|
"@types/webpack-env": "^1.18.0",
|
||||||
"@types/webpack-node-externals": "^2.5.3",
|
"@types/webpack-node-externals": "^2.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.36.2",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"@typescript-eslint/parser": "^5.36.2",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
"adr": "^1.4.1",
|
"adr": "^1.4.3",
|
||||||
"ansi_up": "^5.1.0",
|
"ansi_up": "^5.1.0",
|
||||||
|
"chalk": "^4.1.2",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
"cli-progress": "^3.11.2",
|
"cli-progress": "^3.11.2",
|
||||||
"color": "^3.2.1",
|
"color": "^3.2.1",
|
||||||
"command-line-args": "^5.2.1",
|
"command-line-args": "^5.2.1",
|
||||||
"concurrently": "^7.4.0",
|
"concurrently": "^7.6.0",
|
||||||
"css-loader": "^6.7.1",
|
"css-loader": "^6.7.2",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.4.0",
|
"dompurify": "^2.4.1",
|
||||||
"electron": "^19.0.16",
|
"electron": "^19.1.7",
|
||||||
"electron-builder": "^23.3.3",
|
"electron-builder": "^23.6.0",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild": "^0.15.7",
|
"esbuild": "^0.15.15",
|
||||||
"esbuild-loader": "^2.20.0",
|
"esbuild-loader": "^2.20.0",
|
||||||
"eslint": "^8.23.0",
|
"eslint": "^8.28.0",
|
||||||
"eslint-plugin-header": "^3.1.1",
|
"eslint-plugin-header": "^3.1.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-react": "7.31.7",
|
"eslint-plugin-react": "7.31.11",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"flex.box": "^3.4.4",
|
"eslint-import-resolver-typescript": "^3.5.2",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.5.2",
|
"fork-ts-checker-webpack-plugin": "^6.5.2",
|
||||||
"gunzip-maybe": "^1.4.2",
|
"gunzip-maybe": "^1.4.2",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
@ -397,44 +394,44 @@
|
|||||||
"jest": "^28.1.3",
|
"jest": "^28.1.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
"jest-environment-jsdom": "^28.1.3",
|
"jest-environment-jsdom": "^28.1.3",
|
||||||
"jest-fetch-mock": "^3.0.3",
|
"jest-mock-extended": "^2.0.9",
|
||||||
"jest-mock-extended": "^2.0.7",
|
|
||||||
"make-plural": "^6.2.2",
|
"make-plural": "^6.2.2",
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"memorystream": "^0.3.1",
|
||||||
|
"mini-css-extract-plugin": "^2.7.0",
|
||||||
"mock-http": "^1.1.0",
|
"mock-http": "^1.1.0",
|
||||||
"node-gyp": "^8.3.0",
|
"node-gyp": "^8.3.0",
|
||||||
"node-loader": "^2.0.0",
|
"node-loader": "^2.0.0",
|
||||||
"nodemon": "^2.0.19",
|
"nodemon": "^2.0.20",
|
||||||
"playwright": "^1.25.2",
|
"playwright": "^1.28.1",
|
||||||
"postcss": "^8.4.16",
|
"postcss": "^8.4.19",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"randomcolor": "^0.6.2",
|
"randomcolor": "^0.6.2",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"react-refresh-typescript": "^2.0.7",
|
"react-refresh-typescript": "^2.0.7",
|
||||||
"react-router-dom": "^5.3.3",
|
"react-router-dom": "^5.3.4",
|
||||||
"react-select": "^5.4.0",
|
"react-select": "^5.6.1",
|
||||||
"react-select-event": "^5.5.1",
|
"react-select-event": "^5.5.1",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"react-window": "^1.8.7",
|
"react-window": "^1.8.8",
|
||||||
"sass": "^1.54.9",
|
"sass": "^1.56.1",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sharp": "^0.31.0",
|
"sharp": "^0.31.2",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.2.4",
|
||||||
"tar-stream": "^2.2.0",
|
"tar-stream": "^2.2.0",
|
||||||
"ts-loader": "^9.3.1",
|
"ts-loader": "^9.4.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"type-fest": "^2.14.0",
|
"type-fest": "^2.14.0",
|
||||||
"typed-emitter": "^1.4.0",
|
"typed-emitter": "^1.4.0",
|
||||||
"typedoc": "0.23.14",
|
"typedoc": "0.23.21",
|
||||||
"typedoc-plugin-markdown": "^3.13.1",
|
"typedoc-plugin-markdown": "^3.13.6",
|
||||||
"typescript": "^4.8.2",
|
"typescript": "^4.9.3",
|
||||||
"typescript-plugin-css-modules": "^3.4.0",
|
"typescript-plugin-css-modules": "^3.4.0",
|
||||||
"webpack": "^5.74.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-cli": "^4.9.2",
|
"webpack-cli": "^4.9.2",
|
||||||
"webpack-dev-server": "^4.11.0",
|
"webpack-dev-server": "^4.11.1",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"xterm": "^4.19.0",
|
"xterm": "^4.19.0",
|
||||||
"xterm-addon-fit": "^0.5.0"
|
"xterm-addon-fit": "^0.5.0"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import fse from "fs-extra";
|
|||||||
import { basename } from "path";
|
import { basename } from "path";
|
||||||
import { createInterface } from "readline";
|
import { createInterface } from "readline";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { inspect, promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SemVer,
|
SemVer,
|
||||||
@ -27,6 +27,10 @@ const options = commandLineArgs([
|
|||||||
{
|
{
|
||||||
name: "preid",
|
name: "preid",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "check-commits",
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const validReleaseValues = [
|
const validReleaseValues = [
|
||||||
@ -79,10 +83,22 @@ if (basename(process.cwd()) === "scripts") {
|
|||||||
console.error(errorMessages.wrongCwd);
|
console.error(errorMessages.wrongCwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const packageJson = await fse.readJson("./package.json");
|
||||||
const currentVersion = new SemVer((await fse.readJson("./package.json")).version);
|
const currentVersion = new SemVer(packageJson.version);
|
||||||
|
|
||||||
console.log(`current version: ${currentVersion.format()}`);
|
console.log(`current version: ${currentVersion.format()}`);
|
||||||
|
|
||||||
|
const newVersion = currentVersion.inc(options.type, options.preid);
|
||||||
|
const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
||||||
|
const prBranch = `release/v${newVersion.format()}`;
|
||||||
|
|
||||||
|
await fse.writeJson("./package.json", { ...packageJson, version: newVersion.format() }, { spaces: 2 });
|
||||||
|
await exec(`git checkout -b ${prBranch}`);
|
||||||
|
await exec("git add package.json");
|
||||||
|
await exec(`git commit -sm "Release ${newVersion.format()}"`);
|
||||||
|
|
||||||
|
console.log(`new version: ${newVersion.format()}`);
|
||||||
|
|
||||||
console.log("fetching tags...");
|
console.log("fetching tags...");
|
||||||
await exec("git fetch --tags --force");
|
await exec("git fetch --tags --force");
|
||||||
|
|
||||||
@ -93,25 +109,6 @@ const [previousReleasedVersion] = actualTags
|
|||||||
.sort((l, r) => semverRcompare(l, r))
|
.sort((l, r) => semverRcompare(l, r))
|
||||||
.filter(version => semverLte(version, currentVersion));
|
.filter(version => semverLte(version, currentVersion));
|
||||||
|
|
||||||
const npmVersionArgs = [
|
|
||||||
"npm",
|
|
||||||
"version",
|
|
||||||
options.type,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (options.preid) {
|
|
||||||
npmVersionArgs.push(`--preid=${options.preid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
npmVersionArgs.push("--git-tag-version false");
|
|
||||||
|
|
||||||
await exec(npmVersionArgs.join(" "));
|
|
||||||
|
|
||||||
const newVersion = new SemVer((await fse.readJson("./package.json")).version);
|
|
||||||
const newVersionMilestone = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
|
||||||
|
|
||||||
console.log(`new version: ${newVersion.format()}`);
|
|
||||||
|
|
||||||
const getMergedPrsArgs = [
|
const getMergedPrsArgs = [
|
||||||
"gh",
|
"gh",
|
||||||
"pr",
|
"pr",
|
||||||
@ -146,6 +143,10 @@ interface GithubPrData {
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExtendedGithubPrData extends Omit<GithubPrData, "mergedAt"> {
|
||||||
|
mergedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("retreiving last 500 PRs to create release PR body...");
|
console.log("retreiving last 500 PRs to create release PR body...");
|
||||||
const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[];
|
const mergedPrs = JSON.parse((await exec(getMergedPrsArgs.join(" "), { encoding: "utf-8" })).stdout) as GithubPrData[];
|
||||||
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone);
|
const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone?.title === newVersionMilestone);
|
||||||
@ -159,7 +160,7 @@ const relaventPrs = relaventPrsQuery
|
|||||||
.filter(query => query.stdout)
|
.filter(query => query.stdout)
|
||||||
.map(query => query.pr)
|
.map(query => query.pr)
|
||||||
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"))
|
.filter(pr => pr.labels.every(label => label.name !== "skip-changelog"))
|
||||||
.map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) }))
|
.map(pr => ({ ...pr, mergedAt: new Date(pr.mergedAt) } as ExtendedGithubPrData))
|
||||||
.sort((left, right) => {
|
.sort((left, right) => {
|
||||||
const leftAge = left.mergedAt.valueOf();
|
const leftAge = left.mergedAt.valueOf();
|
||||||
const rightAge = right.mergedAt.valueOf();
|
const rightAge = right.mergedAt.valueOf();
|
||||||
@ -175,75 +176,55 @@ const relaventPrs = relaventPrsQuery
|
|||||||
return -1;
|
return -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(inspect(relaventPrs, false, null, true));
|
|
||||||
|
|
||||||
const enhancementPrLabelName = "enhancement";
|
const enhancementPrLabelName = "enhancement";
|
||||||
const bugfixPrLabelName = "bug";
|
const bugfixPrLabelName = "bug";
|
||||||
|
|
||||||
const enhancementPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === enhancementPrLabelName));
|
const isEnhancementPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === enhancementPrLabelName);
|
||||||
const bugfixPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === bugfixPrLabelName));
|
const isBugfixPr = (pr: ExtendedGithubPrData) => pr.labels.some(label => label.name === bugfixPrLabelName);
|
||||||
const maintenencePrs = relaventPrs.filter(pr => pr.labels.every(label => label.name !== bugfixPrLabelName && label.name !== enhancementPrLabelName));
|
|
||||||
|
|
||||||
console.log("Found:");
|
const prLines = {
|
||||||
console.log(`${enhancementPrs.length} enhancement PRs`);
|
enhancement: [] as string[],
|
||||||
console.log(`${bugfixPrs.length} bug fix PRs`);
|
bugfix: [] as string[],
|
||||||
console.log(`${maintenencePrs.length} maintenence PRs`);
|
maintenence: [] as string[],
|
||||||
|
};
|
||||||
|
|
||||||
const prBodyLines = [
|
function getPrEntry(pr: ExtendedGithubPrData) {
|
||||||
`## Changes since ${previousReleasedVersion}`,
|
|
||||||
"",
|
|
||||||
];
|
|
||||||
|
|
||||||
function getPrEntry(pr) {
|
|
||||||
return `- ${pr.title} (**[#${pr.number}](https://github.com/lensapp/lens/pull/${pr.number})**) https://github.com/${pr.author.login}`;
|
return `- ${pr.title} (**[#${pr.number}](https://github.com/lensapp/lens/pull/${pr.number})**) https://github.com/${pr.author.login}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enhancementPrs.length > 0) {
|
const rl = createInterface(process.stdin);
|
||||||
prBodyLines.push(
|
|
||||||
"## 🚀 Features",
|
|
||||||
"",
|
|
||||||
...enhancementPrs.map(getPrEntry),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bugfixPrs.length > 0) {
|
|
||||||
prBodyLines.push(
|
|
||||||
"## 🐛 Bug Fixes",
|
|
||||||
"",
|
|
||||||
...bugfixPrs.map(getPrEntry),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maintenencePrs.length > 0) {
|
|
||||||
prBodyLines.push(
|
|
||||||
"## 🧰 Maintenance",
|
|
||||||
"",
|
|
||||||
...maintenencePrs.map(getPrEntry),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prBody = prBodyLines.join("\n");
|
|
||||||
const prBase = newVersion.patch === 0
|
const prBase = newVersion.patch === 0
|
||||||
? "master"
|
? "master"
|
||||||
: `release/v${newVersion.major}.${newVersion.minor}`;
|
: `release/v${newVersion.major}.${newVersion.minor}`;
|
||||||
const createPrArgs = [
|
|
||||||
"pr",
|
|
||||||
"create",
|
|
||||||
"--base", prBase,
|
|
||||||
"--title", `release ${newVersion.format()}`,
|
|
||||||
"--label", "skip-changelog",
|
|
||||||
"--body-file", "-",
|
|
||||||
];
|
|
||||||
|
|
||||||
const rl = createInterface(process.stdin);
|
function askQuestion(question: string): Promise<boolean> {
|
||||||
|
return new Promise<boolean>(resolve => {
|
||||||
|
function _askQuestion() {
|
||||||
|
console.log(question);
|
||||||
|
|
||||||
if (prBase !== "master") {
|
rl.once("line", (answer) => {
|
||||||
console.log("Cherry-picking commits to current branch");
|
const cleaned = answer.trim().toLowerCase();
|
||||||
|
|
||||||
for (const pr of relaventPrs) {
|
if (cleaned === "y") {
|
||||||
|
resolve(true);
|
||||||
|
} else if (cleaned === "n") {
|
||||||
|
resolve(false);
|
||||||
|
} else {
|
||||||
|
_askQuestion();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_askQuestion();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRelaventPr(pr: ExtendedGithubPrData) {
|
||||||
|
if (options["check-commits"] && !(await askQuestion(`Would you like to use #${pr.number}: ${pr.title}? - Y/N`))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prBase !== "master") {
|
||||||
try {
|
try {
|
||||||
const promise = exec(`git cherry-pick ${pr.mergeCommit.oid}`);
|
const promise = exec(`git cherry-pick ${pr.mergeCommit.oid}`);
|
||||||
|
|
||||||
@ -255,11 +236,72 @@ if (prBase !== "master") {
|
|||||||
await promise;
|
await promise;
|
||||||
} catch {
|
} catch {
|
||||||
console.error(`Failed to cherry-pick ${pr.mergeCommit.oid}, please resolve conflicts and then press enter here:`);
|
console.error(`Failed to cherry-pick ${pr.mergeCommit.oid}, please resolve conflicts and then press enter here:`);
|
||||||
await new Promise<void>(resolve => rl.on("line", () => resolve()));
|
await new Promise<void>(resolve => rl.once("line", () => resolve()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEnhancementPr(pr)) {
|
||||||
|
prLines.enhancement.push(getPrEntry(pr));
|
||||||
|
} else if (isBugfixPr(pr)) {
|
||||||
|
prLines.bugfix.push(getPrEntry(pr));
|
||||||
|
} else {
|
||||||
|
prLines.maintenence.push(getPrEntry(pr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const pr of relaventPrs) {
|
||||||
|
await handleRelaventPr(pr);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
const prBodyLines = [
|
||||||
|
`## Changes since ${previousReleasedVersion}`,
|
||||||
|
"",
|
||||||
|
...(
|
||||||
|
prLines.enhancement.length > 0
|
||||||
|
? [
|
||||||
|
"## 🚀 Features",
|
||||||
|
"",
|
||||||
|
...prLines.enhancement,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
...(
|
||||||
|
prLines.bugfix.length > 0
|
||||||
|
? [
|
||||||
|
"## 🐛 Bug Fixes",
|
||||||
|
"",
|
||||||
|
...prLines.bugfix,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
...(
|
||||||
|
prLines.maintenence.length > 0
|
||||||
|
? [
|
||||||
|
"## 🧰 Maintenance",
|
||||||
|
"",
|
||||||
|
...prLines.maintenence,
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const prBody = prBodyLines.join("\n");
|
||||||
|
const createPrArgs = [
|
||||||
|
"pr",
|
||||||
|
"create",
|
||||||
|
"--base", prBase,
|
||||||
|
"--title", `Release ${newVersion.format()}`,
|
||||||
|
"--label", "skip-changelog",
|
||||||
|
"--label", "release",
|
||||||
|
"--body-file", "-",
|
||||||
|
];
|
||||||
|
|
||||||
|
await exec(`git push --set-upstream origin ${prBranch}`);
|
||||||
|
|
||||||
const createPrProcess = execFile("gh", createPrArgs);
|
const createPrProcess = execFile("gh", createPrArgs);
|
||||||
|
|
||||||
createPrProcess.child.stdout?.pipe(process.stdout);
|
createPrProcess.child.stdout?.pipe(process.stdout);
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
key="$1"
|
|
||||||
|
|
||||||
case $key in
|
|
||||||
-f|--force)
|
|
||||||
FORCE="--force"
|
|
||||||
shift # past argument
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ `git branch --show-current` =~ ^release/v ]]
|
|
||||||
then
|
|
||||||
VERSION_STRING=$(cat package.json | jq '.version' -r | xargs printf "v%s")
|
|
||||||
git tag ${VERSION_STRING} ${FORCE}
|
|
||||||
git push ${GIT_REMOTE:-origin} ${VERSION_STRING} ${FORCE}
|
|
||||||
else
|
|
||||||
echo "You must be in a release branch"
|
|
||||||
fi
|
|
||||||
@ -18,13 +18,13 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection
|
|||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../vars/app-version.injectable";
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable";
|
||||||
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
|
import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable";
|
||||||
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
|
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
|
||||||
import fsInjectable from "../fs/fs.injectable";
|
import fsInjectable from "../fs/fs.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ users:
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
mainDi.override(appVersionInjectable, () => "3.6.0");
|
mainDi.override(storeMigrationVersionInjectable, () => "3.6.0");
|
||||||
|
|
||||||
createCluster = mainDi.inject(createClusterInjectionToken);
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { AppEvent } from "../app-event-bus/event-bus";
|
|
||||||
import { appEventBus } from "../app-event-bus/event-bus";
|
|
||||||
import { assert, Console } from "console";
|
|
||||||
import { stdout, stderr } from "process";
|
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
|
||||||
|
|
||||||
describe("event bus tests", () => {
|
|
||||||
describe("emit", () => {
|
|
||||||
it("emits an event", () => {
|
|
||||||
let event: AppEvent | undefined;
|
|
||||||
|
|
||||||
appEventBus.addListener((data) => {
|
|
||||||
event = data;
|
|
||||||
});
|
|
||||||
|
|
||||||
appEventBus.emit({ name: "foo", action: "bar" });
|
|
||||||
assert(event);
|
|
||||||
expect(event?.name).toBe("foo");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -8,7 +8,6 @@ import mockFs from "mock-fs";
|
|||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../vars/app-version.injectable";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
import hotbarStoreInjectable from "../hotbars/store.injectable";
|
||||||
import type { HotbarStore } from "../hotbars/store";
|
import type { HotbarStore } from "../hotbars/store";
|
||||||
@ -19,6 +18,7 @@ import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-
|
|||||||
import loggerInjectable from "../logger.injectable";
|
import loggerInjectable from "../logger.injectable";
|
||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
|
||||||
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
function getMockCatalogEntity(data: Partial<CatalogEntityData> & CatalogEntityKindData): CatalogEntity {
|
||||||
return {
|
return {
|
||||||
@ -348,7 +348,7 @@ describe("HotbarStore", () => {
|
|||||||
|
|
||||||
mockFs(configurationToBeMigrated);
|
mockFs(configurationToBeMigrated);
|
||||||
|
|
||||||
di.override(appVersionInjectable, () => "5.0.0-beta.10");
|
di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10");
|
||||||
|
|
||||||
hotbarStore = di.inject(hotbarStoreInjectable);
|
hotbarStore = di.inject(hotbarStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import https from "https";
|
|
||||||
import os from "os";
|
|
||||||
import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca";
|
|
||||||
import { dependencies, devDependencies } from "../../../package.json";
|
|
||||||
import assert from "assert";
|
|
||||||
|
|
||||||
const deps = { ...dependencies, ...devDependencies };
|
|
||||||
|
|
||||||
// Skip the test if mac-ca is not installed, or os is not darwin
|
|
||||||
(deps["mac-ca"] && os.platform().includes("darwin") ? describe: describe.skip)("inject CA for Mac", () => {
|
|
||||||
// for reset https.globalAgent.options.ca after testing
|
|
||||||
let _ca: string | Buffer | (string | Buffer)[] | undefined;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
_ca = https.globalAgent.options.ca;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
https.globalAgent.options.ca = _ca;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The test to ensure using getMacRootCA + injectCAs injects CAs in the same way as using
|
|
||||||
* the auto injection (require('mac-ca'))
|
|
||||||
*/
|
|
||||||
it("should inject the same ca as mac-ca", async () => {
|
|
||||||
const osxCAs = await getMacRootCA();
|
|
||||||
|
|
||||||
injectCAs(osxCAs);
|
|
||||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
|
||||||
|
|
||||||
await import("mac-ca");
|
|
||||||
const injectedByMacCA = https.globalAgent.options.ca as (string | Buffer)[];
|
|
||||||
|
|
||||||
expect(new Set(injected)).toEqual(new Set(injectedByMacCA));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't included the expired DST Root CA X3 on Mac", async () => {
|
|
||||||
const osxCAs = await getMacRootCA();
|
|
||||||
|
|
||||||
injectCAs(osxCAs);
|
|
||||||
const injected = https.globalAgent.options.ca;
|
|
||||||
|
|
||||||
assert(injected);
|
|
||||||
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Skip the test if win-ca is not installed, or os is not win32
|
|
||||||
(deps["win-ca"] && os.platform().includes("win32") ? describe: describe.skip)("inject CA for Windows", () => {
|
|
||||||
// for reset https.globalAgent.options.ca after testing
|
|
||||||
let _ca: string | Buffer | (string | Buffer)[] | undefined;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
_ca = https.globalAgent.options.ca;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
https.globalAgent.options.ca = _ca;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The test to ensure using win-ca/api injects CAs in the same way as using
|
|
||||||
* the auto injection (require('win-ca').inject('+'))
|
|
||||||
*/
|
|
||||||
it("should inject the same ca as winca.inject('+')", async () => {
|
|
||||||
const winCAs = await getWinRootCA();
|
|
||||||
|
|
||||||
const wincaAPI = await import("win-ca/api");
|
|
||||||
|
|
||||||
wincaAPI.inject("+", winCAs);
|
|
||||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
|
||||||
|
|
||||||
const winca = await import("win-ca");
|
|
||||||
|
|
||||||
winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats
|
|
||||||
const injectedByWinCA = https.globalAgent.options.ca as (string | Buffer)[];
|
|
||||||
|
|
||||||
expect(new Set(injected)).toEqual(new Set(injectedByWinCA));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't included the expired DST Root CA X3 on Windows", async () => {
|
|
||||||
const winCAs = await getWinRootCA();
|
|
||||||
|
|
||||||
const wincaAPI = await import("win-ca/api");
|
|
||||||
|
|
||||||
wincaAPI.inject("true", winCAs);
|
|
||||||
const injected = https.globalAgent.options.ca as (string | Buffer)[];
|
|
||||||
|
|
||||||
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -23,8 +23,6 @@ jest.mock("electron", () => ({
|
|||||||
|
|
||||||
import type { UserStore } from "../user-store";
|
import type { UserStore } from "../user-store";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { SemVer } from "semver";
|
|
||||||
import electron from "electron";
|
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import userStoreInjectable from "../user-store/user-store.injectable";
|
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
@ -34,7 +32,9 @@ import { defaultThemeId } from "../vars";
|
|||||||
import writeFileInjectable from "../fs/write-file.injectable";
|
import writeFileInjectable from "../fs/write-file.injectable";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "../vars/app-version.injectable";
|
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||||
|
import releaseChannelInjectable from "../vars/release-channel.injectable";
|
||||||
|
import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ describe("user store tests", () => {
|
|||||||
let userStore: UserStore;
|
let userStore: UserStore;
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs();
|
mockFs();
|
||||||
@ -52,6 +52,12 @@ describe("user store tests", () => {
|
|||||||
di.permitSideEffects(getConfigurationFileModelInjectable);
|
di.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
di.permitSideEffects(userStoreInjectable);
|
di.permitSideEffects(userStoreInjectable);
|
||||||
|
|
||||||
|
di.override(releaseChannelInjectable, () => ({
|
||||||
|
get: () => "latest" as const,
|
||||||
|
init: async () => {},
|
||||||
|
}));
|
||||||
|
await di.inject(defaultUpdateChannelInjectable).init();
|
||||||
|
|
||||||
di.unoverride(userStoreInjectable);
|
di.unoverride(userStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -64,6 +70,7 @@ describe("user store tests", () => {
|
|||||||
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
userStore = di.inject(userStoreInjectable);
|
||||||
|
userStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||||
@ -86,13 +93,6 @@ describe("user store tests", () => {
|
|||||||
userStore.resetTheme();
|
userStore.resetTheme();
|
||||||
expect(userStore.colorTheme).toBe(defaultThemeId);
|
expect(userStore.colorTheme).toBe(defaultThemeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly calculates if the last seen version is an old release", () => {
|
|
||||||
expect(userStore.isNewVersion).toBe(true);
|
|
||||||
|
|
||||||
userStore.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
|
||||||
expect(userStore.isNewVersion).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("migrations", () => {
|
describe("migrations", () => {
|
||||||
@ -125,9 +125,10 @@ describe("user store tests", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
di.override(appVersionInjectable, () => "10.0.0");
|
di.override(storeMigrationVersionInjectable, () => "10.0.0");
|
||||||
|
|
||||||
userStore = di.inject(userStoreInjectable);
|
userStore = di.inject(userStoreInjectable);
|
||||||
|
userStore.load();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets last seen app version to 0.0.0", () => {
|
it("sets last seen app version to 0.0.0", () => {
|
||||||
|
|||||||
@ -3,12 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { appEventBus } from "./event-bus";
|
import { EventEmitter } from "../event-emitter";
|
||||||
|
import type { AppEvent } from "./event-bus";
|
||||||
|
|
||||||
const appEventBusInjectable = getInjectable({
|
const appEventBusInjectable = getInjectable({
|
||||||
id: "app-event-bus",
|
id: "app-event-bus",
|
||||||
instantiate: () => appEventBus,
|
instantiate: () => new EventEmitter<[AppEvent]>,
|
||||||
causesSideEffects: true,
|
|
||||||
decorable: false,
|
decorable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,18 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import appEventBusInjectable from "./app-event-bus.injectable";
|
import appEventBusInjectable from "./app-event-bus.injectable";
|
||||||
|
import type { AppEvent } from "./event-bus";
|
||||||
|
|
||||||
const emitEventInjectable = getInjectable({
|
export type EmitAppEvent = (event: AppEvent) => void;
|
||||||
id: "emit-event",
|
|
||||||
instantiate: (di) => di.inject(appEventBusInjectable).emit,
|
const emitAppEventInjectable = getInjectable({
|
||||||
|
id: "emit-app-event",
|
||||||
|
instantiate: (di): EmitAppEvent => {
|
||||||
|
const bus = di.inject(appEventBusInjectable);
|
||||||
|
|
||||||
|
return (event) => bus.emit(event);
|
||||||
|
},
|
||||||
decorable: false,
|
decorable: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default emitEventInjectable;
|
export default emitAppEventInjectable;
|
||||||
|
|||||||
@ -3,13 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from "../event-emitter";
|
/**
|
||||||
|
* Data for telemetry
|
||||||
|
*/
|
||||||
export interface AppEvent {
|
export interface AppEvent {
|
||||||
name: string;
|
name: string;
|
||||||
action: string;
|
action: string;
|
||||||
destination?: string;
|
destination?: string;
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appEventBus = new EventEmitter<[AppEvent]>();
|
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { AppPaths } from "./app-path-injection-token";
|
|
||||||
import type { RequestChannel } from "../utils/channel/request-channel-injection-token";
|
|
||||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
|
||||||
|
|
||||||
export type AppPathsChannel = RequestChannel<void, AppPaths>;
|
|
||||||
|
|
||||||
const appPathsChannelInjectable = getInjectable({
|
|
||||||
id: "app-paths-channel",
|
|
||||||
|
|
||||||
instantiate: (): AppPathsChannel => ({
|
|
||||||
id: "app-paths",
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default appPathsChannelInjectable;
|
|
||||||
13
src/common/app-paths/app-paths-channel.ts
Normal file
13
src/common/app-paths/app-paths-channel.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import type { AppPaths } from "./app-path-injection-token";
|
||||||
|
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export type AppPathsChannel = RequestChannel<void, AppPaths>;
|
||||||
|
|
||||||
|
export const appPathsChannel: AppPathsChannel = {
|
||||||
|
id: "app-paths",
|
||||||
|
};
|
||||||
|
|
||||||
@ -7,7 +7,6 @@ import { appPathsInjectionToken } from "./app-path-injection-token";
|
|||||||
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||||
import type { PathName } from "./app-path-names";
|
import type { PathName } from "./app-path-names";
|
||||||
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
|
|
||||||
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
@ -53,8 +52,6 @@ describe("app-paths", () => {
|
|||||||
defaultAppPathsStub[key] = path;
|
defaultAppPathsStub[key] = path;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
mainDi.override(appNameInjectable, () => "some-app-name");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,7 +85,7 @@ describe("app-paths", () => {
|
|||||||
recent: "some-recent",
|
recent: "some-recent",
|
||||||
temp: "some-temp",
|
temp: "some-temp",
|
||||||
videos: "some-videos",
|
videos: "some-videos",
|
||||||
userData: "some-app-data/some-app-name",
|
userData: "some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -111,7 +108,7 @@ describe("app-paths", () => {
|
|||||||
recent: "some-recent",
|
recent: "some-recent",
|
||||||
temp: "some-temp",
|
temp: "some-temp",
|
||||||
videos: "some-videos",
|
videos: "some-videos",
|
||||||
userData: "some-app-data/some-app-name",
|
userData: "some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -137,7 +134,7 @@ describe("app-paths", () => {
|
|||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
appData: "some-integration-testing-app-data",
|
appData: "some-integration-testing-app-data",
|
||||||
userData: `some-integration-testing-app-data/some-app-name`,
|
userData: `some-integration-testing-app-data/some-product-name`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,7 +143,7 @@ describe("app-paths", () => {
|
|||||||
|
|
||||||
expect({ appData, userData }).toEqual({
|
expect({ appData, userData }).toEqual({
|
||||||
appData: "some-integration-testing-app-data",
|
appData: "some-integration-testing-app-data",
|
||||||
userData: "some-integration-testing-app-data/some-app-name",
|
userData: "some-integration-testing-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,16 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||||
|
|
||||||
const directoryForBinariesInjectable = getInjectable({
|
const directoryForBinariesInjectable = getInjectable({
|
||||||
id: "directory-for-binaries",
|
id: "directory-for-binaries",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||||
|
|
||||||
return getAbsolutePath(directoryForUserData, "binaries");
|
return joinPaths(directoryForUserData, "binaries");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,19 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||||
|
|
||||||
const directoryForKubeConfigsInjectable = getInjectable({
|
const directoryForKubeConfigsInjectable = getInjectable({
|
||||||
id: "directory-for-kube-configs",
|
id: "directory-for-kube-configs",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||||
|
|
||||||
return getAbsolutePath(
|
return joinPaths(directoryForUserData, "kubeconfigs");
|
||||||
directoryForUserData,
|
|
||||||
"kubeconfigs",
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,17 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForBinariesInjectable from "../directory-for-binaries/directory-for-binaries.injectable";
|
import directoryForBinariesInjectable from "../directory-for-binaries/directory-for-binaries.injectable";
|
||||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||||
|
|
||||||
const directoryForKubectlBinariesInjectable = getInjectable({
|
const directoryForKubectlBinariesInjectable = getInjectable({
|
||||||
id: "directory-for-kubectl-binaries",
|
id: "directory-for-kubectl-binaries",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
const directoryForBinaries = di.inject(directoryForBinariesInjectable);
|
const directoryForBinaries = di.inject(directoryForBinariesInjectable);
|
||||||
|
|
||||||
|
return joinPaths(directoryForBinaries, "kubectl");
|
||||||
return getAbsolutePath(directoryForBinaries, "kubectl");
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
13
src/common/app-paths/directory-for-logs.injectable.ts
Normal file
13
src/common/app-paths/directory-for-logs.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import appPathsInjectable from "./app-paths.injectable";
|
||||||
|
|
||||||
|
const directoryForLogsInjectable = getInjectable({
|
||||||
|
id: "directory-for-logs",
|
||||||
|
instantiate: (di) => di.inject(appPathsInjectable).logs,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForLogsInjectable;
|
||||||
@ -4,17 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||||
|
|
||||||
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
||||||
id: "get-custom-kube-config-directory",
|
id: "get-custom-kube-config-directory",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
|
||||||
return (directoryName: string) =>
|
return (directoryName: string) => joinPaths(directoryForKubeConfigs, directoryName);
|
||||||
getAbsolutePath(directoryForKubeConfigs, directoryName);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { MessageChannel } from "../../utils/channel/message-channel-injection-token";
|
|
||||||
import { messageChannelInjectionToken } from "../../utils/channel/message-channel-injection-token";
|
|
||||||
|
|
||||||
export type RestartAndInstallUpdateChannel = MessageChannel;
|
|
||||||
|
|
||||||
const restartAndInstallUpdateChannel = getInjectable({
|
|
||||||
id: "restart-and-install-update-channel",
|
|
||||||
|
|
||||||
instantiate: (): RestartAndInstallUpdateChannel => ({
|
|
||||||
id: "restart-and-install-update-channel",
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default restartAndInstallUpdateChannel;
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import appSemanticVersionInjectable from "../../vars/app-semantic-version.injectable";
|
|
||||||
import type { UpdateChannelId } from "../update-channels";
|
|
||||||
import { updateChannels } from "../update-channels";
|
|
||||||
|
|
||||||
const defaultUpdateChannelInjectable = getInjectable({
|
|
||||||
id: "default-update-channel",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const appSemanticVersion = di.inject(appSemanticVersionInjectable);
|
|
||||||
const currentReleaseChannel = appSemanticVersion.prerelease[0]?.toString();
|
|
||||||
|
|
||||||
if (currentReleaseChannel in updateChannels) {
|
|
||||||
return updateChannels[currentReleaseChannel as UpdateChannelId];
|
|
||||||
}
|
|
||||||
|
|
||||||
return updateChannels.latest;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defaultUpdateChannelInjectable;
|
|
||||||
@ -19,7 +19,7 @@ import { kebabCase } from "lodash";
|
|||||||
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
import getConfigurationFileModelInjectable from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
||||||
import appVersionInjectable from "./vars/app-version.injectable";
|
import storeMigrationVersionInjectable from "./vars/store-migration-version.injectable";
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||||
syncOptions?: {
|
syncOptions?: {
|
||||||
@ -60,7 +60,7 @@ export abstract class BaseStore<T extends object> extends Singleton {
|
|||||||
|
|
||||||
this.storeConfig = getConfigurationFileModel({
|
this.storeConfig = getConfigurationFileModel({
|
||||||
projectName: "lens",
|
projectName: "lens",
|
||||||
projectVersion: di.inject(appVersionInjectable),
|
projectVersion: di.inject(storeMigrationVersionInjectable),
|
||||||
cwd: this.cwd(),
|
cwd: this.cwd(),
|
||||||
...this.params,
|
...this.params,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,9 +3,10 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
|
||||||
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
|
||||||
import { productName } from "../vars";
|
import productNameInjectable from "../vars/product-name.injectable";
|
||||||
import { WeblinkStore } from "../weblink-store";
|
import { WeblinkStore } from "../weblink-store";
|
||||||
|
|
||||||
export type WebLinkStatusPhase = "available" | "unavailable";
|
export type WebLinkStatusPhase = "available" | "unavailable";
|
||||||
@ -30,6 +31,9 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
onContextMenuOpen(context: CatalogEntityContextMenuContext) {
|
||||||
|
const di = getLegacyGlobalDiForExtensionApi();
|
||||||
|
const productName = di.inject(productNameInjectable);
|
||||||
|
|
||||||
if (this.metadata.source === "local") {
|
if (this.metadata.source === "local") {
|
||||||
context.menuItems.push({
|
context.menuItems.push({
|
||||||
title: "Delete",
|
title: "Delete",
|
||||||
|
|||||||
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { computed } from "mobx";
|
||||||
|
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
||||||
|
|
||||||
|
const filteredCategoriesInjectable = getInjectable({
|
||||||
|
id: "filtered-categories",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const registry = di.inject(catalogCategoryRegistryInjectable);
|
||||||
|
|
||||||
|
return computed(() => [...registry.filteredItems]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default filteredCategoriesInjectable;
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { globalAgent } from "https";
|
||||||
|
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||||
|
|
||||||
|
// DST Root CA X3, which was expired on 9.30.2021
|
||||||
|
const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
|
||||||
|
|
||||||
|
function isCertActive(cert: string) {
|
||||||
|
const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3);
|
||||||
|
|
||||||
|
return !isExpired;
|
||||||
|
}
|
||||||
|
|
||||||
|
const injectSystemCAsInjectable = getInjectable({
|
||||||
|
id: "inject-system-cas",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const requestSystemCAs = di.inject(requestSystemCAsInjectionToken);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
const certs = await requestSystemCAs();
|
||||||
|
|
||||||
|
if (certs.length === 0) {
|
||||||
|
// Leave the global option alone
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cas = (() => {
|
||||||
|
if (Array.isArray(globalAgent.options.ca)) {
|
||||||
|
return globalAgent.options.ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalAgent.options.ca) {
|
||||||
|
return [globalAgent.options.ca];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
})();
|
||||||
|
|
||||||
|
for (const cert of certs) {
|
||||||
|
if (!isCertActive(cert)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cas.includes(cert)) {
|
||||||
|
cas.push(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalAgent.options.ca = cas;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectSystemCAsInjectable;
|
||||||
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export const requestSystemCAsInjectionToken = getInjectionToken<() => Promise<string[]>>({
|
||||||
|
id: "request-system-cas-token",
|
||||||
|
});
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import execFileInjectable from "../fs/exec-file.injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { AsyncResult } from "../utils/async-result";
|
||||||
|
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions
|
||||||
|
const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
|
||||||
|
|
||||||
|
const requestSystemCAsInjectable = getInjectable({
|
||||||
|
id: "request-system-cas",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const execFile = di.inject(execFileInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
const execSecurity = async (...args: string[]): Promise<AsyncResult<string[]>> => {
|
||||||
|
const result = await execFile("/usr/bin/security", args);
|
||||||
|
|
||||||
|
if (!result.callWasSuccessful) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: result.error.stderr || result.error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
callWasSuccessful: true,
|
||||||
|
response: result.response.split(certSplitPattern),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
const [trustedResult, rootCAResult] = await Promise.all([
|
||||||
|
execSecurity("find-certificate", "-a", "-p"),
|
||||||
|
execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!trustedResult.callWasSuccessful) {
|
||||||
|
logger.warn(`[INJECT-CAS]: Error retreiving trusted CAs: ${trustedResult.error}`);
|
||||||
|
} else if (!rootCAResult.callWasSuccessful) {
|
||||||
|
logger.warn(`[INJECT-CAS]: Error retreiving root CAs: ${rootCAResult.error}`);
|
||||||
|
} else {
|
||||||
|
return [...new Set([...trustedResult.response, ...rootCAResult.response])];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
causesSideEffects: true,
|
||||||
|
injectionToken: requestSystemCAsInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestSystemCAsInjectable;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||||
|
|
||||||
|
const requestSystemCAsInjectable = getInjectable({
|
||||||
|
id: "request-system-cas",
|
||||||
|
instantiate: () => async () => [],
|
||||||
|
injectionToken: requestSystemCAsInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestSystemCAsInjectable;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||||
|
|
||||||
|
const requestSystemCAsInjectable = getInjectable({
|
||||||
|
id: "request-system-cas",
|
||||||
|
instantiate: () => async () => [],
|
||||||
|
injectionToken: requestSystemCAsInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestSystemCAsInjectable;
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import execFileInjectable from "../fs/exec-file.injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
|
||||||
|
|
||||||
|
const pemEncoding = (hexEncodedCert: String) => {
|
||||||
|
const certData = Buffer.from(hexEncodedCert, "hex").toString("base64");
|
||||||
|
const lines = ["-----BEGIN CERTIFICATE-----"];
|
||||||
|
|
||||||
|
for (let i = 0; i < certData.length; i += 64) {
|
||||||
|
lines.push(certData.substring(i, i + 64));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push("-----END CERTIFICATE-----", "");
|
||||||
|
|
||||||
|
return lines.join("\r\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestSystemCAsInjectable = getInjectable({
|
||||||
|
id: "request-system-cas",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const wincaRootsExePath: string = __non_webpack_require__.resolve("win-ca/lib/roots.exe");
|
||||||
|
const execFile = di.inject(execFileInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
/**
|
||||||
|
* This needs to be done manually because for some reason calling the api from "win-ca"
|
||||||
|
* directly fails to load "child_process" correctly on renderer
|
||||||
|
*/
|
||||||
|
const result = await execFile(wincaRootsExePath, {
|
||||||
|
maxBuffer: 128 * 1024 * 1024, // 128 MiB
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.callWasSuccessful) {
|
||||||
|
logger.warn(`[INJECT-CAS]: Error retreiving CAs`, result.error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
.response
|
||||||
|
.split("\r\n")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(pemEncoding);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
causesSideEffects: true,
|
||||||
|
injectionToken: requestSystemCAsInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default requestSystemCAsInjectable;
|
||||||
@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import { ClusterStore } from "./cluster-store";
|
import { ClusterStore } from "./cluster-store";
|
||||||
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
|
||||||
|
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
||||||
|
|
||||||
const clusterStoreInjectable = getInjectable({
|
const clusterStoreInjectable = getInjectable({
|
||||||
id: "cluster-store",
|
id: "cluster-store",
|
||||||
@ -16,6 +17,7 @@ const clusterStoreInjectable = getInjectable({
|
|||||||
return ClusterStore.createInstance({
|
return ClusterStore.createInstance({
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
createCluster: di.inject(createClusterInjectionToken),
|
||||||
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
||||||
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { BaseStore } from "../base-store";
|
|||||||
import { Cluster } from "../cluster/cluster";
|
import { Cluster } from "../cluster/cluster";
|
||||||
import migrations from "../../migrations/cluster-store";
|
import migrations from "../../migrations/cluster-store";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { appEventBus } from "../app-event-bus/event-bus";
|
|
||||||
import { ipcMainHandle } from "../ipc";
|
import { ipcMainHandle } from "../ipc";
|
||||||
import { disposer, toJS } from "../utils";
|
import { disposer, toJS } from "../utils";
|
||||||
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
||||||
@ -18,6 +17,7 @@ import { requestInitialClusterStates } from "../../renderer/ipc";
|
|||||||
import { clusterStates } from "../ipc/cluster";
|
import { clusterStates } from "../ipc/cluster";
|
||||||
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
|
||||||
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
|
||||||
|
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
clusters?: ClusterModel[];
|
clusters?: ClusterModel[];
|
||||||
@ -26,6 +26,7 @@ export interface ClusterStoreModel {
|
|||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
createCluster: CreateCluster;
|
createCluster: CreateCluster;
|
||||||
readClusterConfigSync: ReadClusterConfigSync;
|
readClusterConfigSync: ReadClusterConfigSync;
|
||||||
|
emitAppEvent: EmitAppEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||||
@ -34,7 +35,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
protected disposer = disposer();
|
protected disposer = disposer();
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
super({
|
super({
|
||||||
configName: "lens-cluster-store",
|
configName: "lens-cluster-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -115,7 +116,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
addCluster(clusterOrModel: ClusterModel | Cluster): Cluster {
|
||||||
appEventBus.emit({ name: "cluster", action: "add" });
|
this.dependencies.emitAppEvent({ name: "cluster", action: "add" });
|
||||||
|
|
||||||
const cluster = clusterOrModel instanceof Cluster
|
const cluster = clusterOrModel instanceof Cluster
|
||||||
? clusterOrModel
|
? clusterOrModel
|
||||||
|
|||||||
21
src/common/cluster-store/get-by-id.injectable.ts
Normal file
21
src/common/cluster-store/get-by-id.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterId } from "../cluster-types";
|
||||||
|
import type { Cluster } from "../cluster/cluster";
|
||||||
|
import clusterStoreInjectable from "./cluster-store.injectable";
|
||||||
|
|
||||||
|
export type GetClusterById = (id: ClusterId) => Cluster | undefined;
|
||||||
|
|
||||||
|
const getClusterByIdInjectable = getInjectable({
|
||||||
|
id: "get-cluster-by-id",
|
||||||
|
instantiate: (di): GetClusterById => {
|
||||||
|
const store = di.inject(clusterStoreInjectable);
|
||||||
|
|
||||||
|
return (id) => store.getById(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getClusterByIdInjectable;
|
||||||
@ -3,6 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Joi from "joi";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON serializable metadata type
|
* JSON serializable metadata type
|
||||||
*/
|
*/
|
||||||
@ -27,6 +29,37 @@ export type ClusterId = string;
|
|||||||
*/
|
*/
|
||||||
export type UpdateClusterModel = Omit<ClusterModel, "id">;
|
export type UpdateClusterModel = Omit<ClusterModel, "id">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type validator for `UpdateClusterModel` so that only expected types are present
|
||||||
|
*/
|
||||||
|
export const updateClusterModelChecker = Joi.object<UpdateClusterModel>({
|
||||||
|
kubeConfigPath: Joi.string()
|
||||||
|
.required()
|
||||||
|
.min(1),
|
||||||
|
contextName: Joi.string()
|
||||||
|
.required()
|
||||||
|
.min(1),
|
||||||
|
workspace: Joi.string()
|
||||||
|
.optional(),
|
||||||
|
workspaces: Joi.array()
|
||||||
|
.items(Joi.string()),
|
||||||
|
preferences: Joi.object(),
|
||||||
|
metadata: Joi.object(),
|
||||||
|
accessibleNamespaces: Joi.array()
|
||||||
|
.items(Joi.string()),
|
||||||
|
labels: Joi.object().pattern(Joi.string(), Joi.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type validator for just the `id` fields of `ClusterModel`. The rest is
|
||||||
|
* covered by `updateClusterModelChecker`
|
||||||
|
*/
|
||||||
|
export const clusterModelIdChecker = Joi.object<Pick<ClusterModel, "id">>({
|
||||||
|
id: Joi.string()
|
||||||
|
.required()
|
||||||
|
.min(1),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The model for passing cluster data around, including to disk
|
* The model for passing cluster data around, including to disk
|
||||||
*/
|
*/
|
||||||
@ -162,13 +195,6 @@ export enum ClusterMetricsResourceType {
|
|||||||
*/
|
*/
|
||||||
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
||||||
|
|
||||||
/**
|
|
||||||
* The arguments for requesting to refresh a cluster's metadata
|
|
||||||
*/
|
|
||||||
export interface ClusterRefreshOptions {
|
|
||||||
refreshMetadata?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data representing a cluster's state, for passing between main and renderer
|
* The data representing a cluster's state, for passing between main and renderer
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param namespace The namespace of the resources
|
||||||
|
* @param availableResources List of available resources in the cluster to resolve glob values fir api groups
|
||||||
|
* @returns list of allowed resources names
|
||||||
|
*/
|
||||||
|
export type RequestNamespaceResources = (namespace: string, availableResources: KubeApiResource[]) => Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
|
*/
|
||||||
|
export type AuthorizationNamespaceReview = (proxyConfig: KubeConfig) => RequestNamespaceResources;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
logger: Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorizationNamespaceReview = ({ logger }: Dependencies): AuthorizationNamespaceReview => {
|
||||||
|
return (proxyConfig) => {
|
||||||
|
|
||||||
|
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||||
|
|
||||||
|
return async (namespace, availableResources) => {
|
||||||
|
try {
|
||||||
|
const { body } = await api.createSelfSubjectRulesReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectRulesReview",
|
||||||
|
spec: { namespace },
|
||||||
|
});
|
||||||
|
|
||||||
|
const resources = new Set<string>();
|
||||||
|
|
||||||
|
body.status?.resourceRules.forEach(resourceRule => {
|
||||||
|
if (!resourceRule.verbs.some(verb => ["*", "list"].includes(verb)) || !resourceRule.resources) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiGroups = resourceRule.apiGroups;
|
||||||
|
|
||||||
|
if (resourceRule.resources.length === 1 && resourceRule.resources[0] === "*" && apiGroups) {
|
||||||
|
if (apiGroups[0] === "*") {
|
||||||
|
availableResources.forEach(resource => resources.add(resource.apiName));
|
||||||
|
} else {
|
||||||
|
availableResources.forEach((apiResource)=> {
|
||||||
|
if (apiGroups.includes(apiResource.group || "")) {
|
||||||
|
resources.add(apiResource.apiName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resourceRule.resources.forEach(resource => resources.add(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...resources];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review: ${error}`, { namespace });
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorizationNamespaceReviewInjectable = getInjectable({
|
||||||
|
id: "authorization-namespace-review",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return authorizationNamespaceReview({ logger });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default authorizationNamespaceReviewInjectable;
|
||||||
@ -5,42 +5,55 @@
|
|||||||
|
|
||||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||||
|
* @returns `true` if the actions described are allowed
|
||||||
|
*/
|
||||||
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
*/
|
*/
|
||||||
export function authorizationReview(proxyConfig: KubeConfig): CanI {
|
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
|
||||||
|
|
||||||
/**
|
interface Dependencies {
|
||||||
* Requests the permissions for actions on the kube cluster
|
logger: Logger;
|
||||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
|
||||||
* @returns `true` if the actions described are allowed
|
|
||||||
*/
|
|
||||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const { body } = await api.createSelfSubjectAccessReview({
|
|
||||||
apiVersion: "authorization.k8s.io/v1",
|
|
||||||
kind: "SelfSubjectAccessReview",
|
|
||||||
spec: { resourceAttributes },
|
|
||||||
});
|
|
||||||
|
|
||||||
return body.status?.allowed ?? false;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
||||||
|
return (proxyConfig) => {
|
||||||
|
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||||
|
|
||||||
|
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const { body } = await api.createSelfSubjectAccessReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectAccessReview",
|
||||||
|
spec: { resourceAttributes },
|
||||||
|
});
|
||||||
|
|
||||||
|
return body.status?.allowed ?? false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const authorizationReviewInjectable = getInjectable({
|
const authorizationReviewInjectable = getInjectable({
|
||||||
id: "authorization-review",
|
id: "authorization-review",
|
||||||
instantiate: () => authorizationReview,
|
instantiate: (di) => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return authorizationReview({ logger });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default authorizationReviewInjectable;
|
export default authorizationReviewInjectable;
|
||||||
|
|||||||
@ -9,14 +9,13 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
|||||||
import { HttpError } from "@kubernetes/client-node";
|
import { HttpError } from "@kubernetes/client-node";
|
||||||
import type { Kubectl } from "../../main/kubectl/kubectl";
|
import type { Kubectl } from "../../main/kubectl/kubectl";
|
||||||
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
||||||
import { loadConfigFromFile } from "../kube-helpers";
|
|
||||||
import type { KubeApiResource, KubeResource } from "../rbac";
|
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||||
import { apiResourceRecord, apiResources } from "../rbac";
|
import { apiResourceRecord, apiResources } from "../rbac";
|
||||||
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||||
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
import type { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
||||||
import plimit from "p-limit";
|
import plimit from "p-limit";
|
||||||
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
import type { ClusterState, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
|
||||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
|
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
|
||||||
import { disposer, isDefined, isRequestError, toJS } from "../utils";
|
import { disposer, isDefined, isRequestError, toJS } from "../utils";
|
||||||
import type { Response } from "request";
|
import type { Response } from "request";
|
||||||
import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster";
|
import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster";
|
||||||
@ -25,6 +24,9 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
|
|||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import type { Logger } from "../logger";
|
import type { Logger } from "../logger";
|
||||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||||
|
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||||
|
import type { RequestNamespaceResources } from "./authorization-namespace-review.injectable";
|
||||||
|
import type { RequestListApiResources } from "./list-api-resources.injectable";
|
||||||
|
|
||||||
export interface ClusterDependencies {
|
export interface ClusterDependencies {
|
||||||
readonly directoryForKubeConfigs: string;
|
readonly directoryForKubeConfigs: string;
|
||||||
@ -34,9 +36,12 @@ export interface ClusterDependencies {
|
|||||||
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||||
createKubectl: (clusterVersion: string) => Kubectl;
|
createKubectl: (clusterVersion: string) => Kubectl;
|
||||||
createAuthorizationReview: (config: KubeConfig) => CanI;
|
createAuthorizationReview: (config: KubeConfig) => CanI;
|
||||||
|
createAuthorizationNamespaceReview: (config: KubeConfig) => RequestNamespaceResources;
|
||||||
|
createListApiResources: (cluster: Cluster) => RequestListApiResources;
|
||||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||||
broadcastMessage: BroadcastMessage;
|
broadcastMessage: BroadcastMessage;
|
||||||
|
loadConfigfromFile: LoadConfigfromFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -237,9 +242,16 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
return this.preferences.defaultNamespace;
|
return this.preferences.defaultNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly dependencies: ClusterDependencies, model: ClusterModel, configData: ClusterConfigData) {
|
constructor(private readonly dependencies: ClusterDependencies, { id, ...model }: ClusterModel, configData: ClusterConfigData) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.id = model.id;
|
|
||||||
|
const { error } = clusterModelIdChecker.validate({ id });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
this.apiUrl = configData.clusterServerUrl;
|
this.apiUrl = configData.clusterServerUrl;
|
||||||
|
|
||||||
@ -261,6 +273,12 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
@action updateModel(model: UpdateClusterModel) {
|
@action updateModel(model: UpdateClusterModel) {
|
||||||
// Note: do not assign ID as that should never be updated
|
// Note: do not assign ID as that should never be updated
|
||||||
|
|
||||||
|
const { error } = updateClusterModelChecker.validate(model, { allowUnknown: true });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
this.kubeConfigPath = model.kubeConfigPath;
|
this.kubeConfigPath = model.kubeConfigPath;
|
||||||
this.contextName = model.contextName;
|
this.contextName = model.contextName;
|
||||||
|
|
||||||
@ -295,7 +313,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
protected bindEvents() {
|
protected bindEvents() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||||
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventsDisposer.push(
|
this.eventsDisposer.push(
|
||||||
reaction(() => this.getState(), state => this.pushState(state)),
|
reaction(() => this.getState(), state => this.pushState(state)),
|
||||||
@ -425,69 +443,71 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* @param opts refresh options
|
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async refresh(opts: ClusterRefreshOptions = {}) {
|
async refresh() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
|
|
||||||
if (this.accessible) {
|
|
||||||
await this.refreshAccessibility();
|
|
||||||
|
|
||||||
if (opts.refreshMetadata) {
|
|
||||||
this.refreshMetadata();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async refreshMetadata() {
|
async refreshAccessibilityAndMetadata() {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
await this.refreshAccessibility();
|
||||||
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
await this.refreshMetadata();
|
||||||
const existingMetadata = this.metadata;
|
|
||||||
|
|
||||||
this.metadata = Object.assign(existingMetadata, metadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
private async refreshAccessibility(): Promise<void> {
|
async refreshMetadata() {
|
||||||
const proxyConfig = await this.getProxyKubeconfig();
|
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||||
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
const metadata = await this.dependencies.detectorRegistry.detectForCluster(this);
|
||||||
|
const existingMetadata = this.metadata;
|
||||||
|
|
||||||
this.isAdmin = await canI({
|
this.metadata = Object.assign(existingMetadata, metadata);
|
||||||
namespace: "kube-system",
|
}
|
||||||
resource: "*",
|
|
||||||
verb: "create",
|
/**
|
||||||
});
|
* @internal
|
||||||
this.isGlobalWatchEnabled = await canI({
|
*/
|
||||||
verb: "watch",
|
private async refreshAccessibility(): Promise<void> {
|
||||||
resource: "*",
|
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta());
|
||||||
});
|
const proxyConfig = await this.getProxyKubeconfig();
|
||||||
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
||||||
this.allowedResources = await this.getAllowedResources(canI);
|
const requestNamespaceResources = this.dependencies.createAuthorizationNamespaceReview(proxyConfig);
|
||||||
this.ready = true;
|
const listApiResources = this.dependencies.createListApiResources(this);
|
||||||
}
|
|
||||||
|
this.isAdmin = await canI({
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "*",
|
||||||
|
verb: "create",
|
||||||
|
});
|
||||||
|
this.isGlobalWatchEnabled = await canI({
|
||||||
|
verb: "watch",
|
||||||
|
resource: "*",
|
||||||
|
});
|
||||||
|
this.allowedNamespaces = await this.getAllowedNamespaces(proxyConfig);
|
||||||
|
this.allowedResources = await this.getAllowedResources(listApiResources, requestNamespaceResources);
|
||||||
|
this.ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
async refreshConnectionStatus() {
|
async refreshConnectionStatus() {
|
||||||
const connectionStatus = await this.getConnectionStatus();
|
const connectionStatus = await this.getConnectionStatus();
|
||||||
|
|
||||||
this.online = connectionStatus > ClusterStatus.Offline;
|
this.online = connectionStatus > ClusterStatus.Offline;
|
||||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getKubeconfig(): Promise<KubeConfig> {
|
async getKubeconfig(): Promise<KubeConfig> {
|
||||||
const { config } = await loadConfigFromFile(this.kubeConfigPath);
|
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@ -497,7 +517,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
*/
|
*/
|
||||||
async getProxyKubeconfig(): Promise<KubeConfig> {
|
async getProxyKubeconfig(): Promise<KubeConfig> {
|
||||||
const proxyKCPath = await this.getProxyKubeconfigPath();
|
const proxyKCPath = await this.getProxyKubeconfigPath();
|
||||||
const { config } = await loadConfigFromFile(proxyKCPath);
|
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@ -653,32 +673,48 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getAllowedResources(canI: CanI) {
|
protected async getAllowedResources(listApiResources:RequestListApiResources, requestNamespaceResources: RequestNamespaceResources) {
|
||||||
try {
|
try {
|
||||||
if (!this.allowedNamespaces.length) {
|
if (!this.allowedNamespaces.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const resources = apiResources.filter((resource) => this.resourceAccessStatuses.get(resource) === undefined);
|
|
||||||
const apiLimit = plimit(5); // 5 concurrent api requests
|
|
||||||
const requests = [];
|
|
||||||
|
|
||||||
for (const apiResource of resources) {
|
const unknownResources = new Map<string, KubeApiResource>(apiResources.map(resource => ([resource.apiName, resource])));
|
||||||
requests.push(apiLimit(async () => {
|
|
||||||
for (const namespace of this.allowedNamespaces.slice(0, 10)) {
|
|
||||||
if (!this.resourceAccessStatuses.get(apiResource)) {
|
|
||||||
const result = await canI({
|
|
||||||
resource: apiResource.apiName,
|
|
||||||
group: apiResource.group,
|
|
||||||
verb: "list",
|
|
||||||
namespace,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.resourceAccessStatuses.set(apiResource, result);
|
const availableResources = await listApiResources();
|
||||||
|
const availableResourcesNames = new Set(availableResources.map(apiResource => apiResource.apiName));
|
||||||
|
|
||||||
|
[...unknownResources.values()].map(unknownResource => {
|
||||||
|
if (!availableResourcesNames.has(unknownResource.apiName)) {
|
||||||
|
this.resourceAccessStatuses.set(unknownResource, false);
|
||||||
|
unknownResources.delete(unknownResource.apiName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (unknownResources.size > 0) {
|
||||||
|
const apiLimit = plimit(5); // 5 concurrent api requests
|
||||||
|
|
||||||
|
await Promise.all(this.allowedNamespaces.map(namespace => apiLimit(async () => {
|
||||||
|
if (unknownResources.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespaceResources = await requestNamespaceResources(namespace, availableResources);
|
||||||
|
|
||||||
|
for (const resourceName of namespaceResources) {
|
||||||
|
const unknownResource = unknownResources.get(resourceName);
|
||||||
|
|
||||||
|
if (unknownResource) {
|
||||||
|
this.resourceAccessStatuses.set(unknownResource, true);
|
||||||
|
unknownResources.delete(resourceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
})));
|
||||||
|
|
||||||
|
for (const forbiddenResource of unknownResources.values()) {
|
||||||
|
this.resourceAccessStatuses.set(forbiddenResource, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(requests);
|
|
||||||
|
|
||||||
return apiResources
|
return apiResources
|
||||||
.filter((resource) => this.resourceAccessStatuses.get(resource))
|
.filter((resource) => this.resourceAccessStatuses.get(resource))
|
||||||
|
|||||||
91
src/common/cluster/list-api-resources.injectable.ts
Normal file
91
src/common/cluster/list-api-resources.injectable.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
V1APIGroupList,
|
||||||
|
V1APIResourceList,
|
||||||
|
V1APIVersions,
|
||||||
|
} from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { K8sRequest } from "../../main/k8s-request.injectable";
|
||||||
|
import k8SRequestInjectable from "../../main/k8s-request.injectable";
|
||||||
|
import type { Logger } from "../logger";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||||
|
import type { Cluster } from "./cluster";
|
||||||
|
import plimit from "p-limit";
|
||||||
|
|
||||||
|
export type RequestListApiResources = () => Promise<KubeApiResource[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
||||||
|
*/
|
||||||
|
export type ListApiResources = (cluster: Cluster) => RequestListApiResources;
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
logger: Logger;
|
||||||
|
k8sRequest: K8sRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listApiResources = ({ k8sRequest, logger }: Dependencies): ListApiResources => {
|
||||||
|
return (cluster) => {
|
||||||
|
const clusterRequest = (path: string) => k8sRequest(cluster, path);
|
||||||
|
const apiLimit = plimit(5);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
const resources: KubeApiResource[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resourceListGroups:{ group:string;path:string }[] = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
[
|
||||||
|
clusterRequest("/api").then((response:V1APIVersions)=>response.versions.forEach(version => resourceListGroups.push({ group:version, path:`/api/${version}` }))),
|
||||||
|
clusterRequest("/apis").then((response:V1APIGroupList) => response.groups.forEach(group => {
|
||||||
|
const preferredVersion = group.preferredVersion?.groupVersion;
|
||||||
|
|
||||||
|
if (preferredVersion) {
|
||||||
|
resourceListGroups.push({ group:group.name, path:`/apis/${preferredVersion}` });
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
resourceListGroups.map(({ group, path }) => apiLimit(async () => {
|
||||||
|
const apiResources:V1APIResourceList = await clusterRequest(path);
|
||||||
|
|
||||||
|
if (apiResources.resources) {
|
||||||
|
resources.push(
|
||||||
|
...apiResources.resources.filter(resource => resource.verbs.includes("list")).map((resource) => ({
|
||||||
|
apiName: resource.name as KubeResource,
|
||||||
|
kind: resource.kind,
|
||||||
|
group,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[LIST-API-RESOURCES]: failed to list api resources: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listApiResourcesInjectable = getInjectable({
|
||||||
|
id: "list-api-resources",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const k8sRequest = di.inject(k8SRequestInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return listApiResources({ k8sRequest, logger });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default listApiResourcesInjectable;
|
||||||
11
src/common/cluster/visibility-channel.ts
Normal file
11
src/common/cluster/visibility-channel.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ClusterId } from "../cluster-types";
|
||||||
|
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export const clusterVisibilityChannel: MessageChannel<ClusterId | null> = {
|
||||||
|
id: "cluster-visibility",
|
||||||
|
};
|
||||||
@ -4,16 +4,16 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
|
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||||
|
|
||||||
const directoryForLensLocalStorageInjectable = getInjectable({
|
const directoryForLensLocalStorageInjectable = getInjectable({
|
||||||
id: "directory-for-lens-local-storage",
|
id: "directory-for-lens-local-storage",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||||
|
|
||||||
return getAbsolutePath(
|
return joinPaths(
|
||||||
directoryForUserData,
|
directoryForUserData,
|
||||||
"lens-local-storage",
|
"lens-local-storage",
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||||
|
import initializeSentryReportingWithInjectable from "./initialize-sentry-reporting.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverride(initializeSentryReportingWithInjectable, () => () => {});
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { ElectronMainOptions } from "@sentry/electron/main";
|
||||||
|
import type { BrowserOptions } from "@sentry/electron/renderer";
|
||||||
|
import isProductionInjectable from "../vars/is-production.injectable";
|
||||||
|
import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable";
|
||||||
|
import { Dedupe, Offline } from "@sentry/integrations";
|
||||||
|
import { inspect } from "util";
|
||||||
|
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||||
|
|
||||||
|
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
||||||
|
|
||||||
|
const mapProcessName = (type: "browser" | "renderer" | "worker") => type === "browser" ? "main" : type;
|
||||||
|
|
||||||
|
const initializeSentryReportingWithInjectable = getInjectable({
|
||||||
|
id: "initialize-sentry-reporting-with",
|
||||||
|
instantiate: (di): InitializeSentryReportingWith => {
|
||||||
|
const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable);
|
||||||
|
const isProduction = di.inject(isProductionInjectable);
|
||||||
|
const userStore = di.inject(userStoreInjectable);
|
||||||
|
|
||||||
|
if (!sentryDataSourceName) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (initSentry) => initSentry({
|
||||||
|
beforeSend: (event) => {
|
||||||
|
if (userStore.allowErrorReporting) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directly write to stdout so that no other integrations capture this and create an infinite loop
|
||||||
|
*/
|
||||||
|
process.stdout.write(`🔒 [SENTRY-BEFORE-SEND-HOOK]: Sentry event is caught but not sent to server.`);
|
||||||
|
process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === START OF SENTRY EVENT ===");
|
||||||
|
process.stdout.write(inspect(event, false, null, true));
|
||||||
|
process.stdout.write("🔒 [SENTRY-BEFORE-SEND-HOOK]: === END OF SENTRY EVENT ===");
|
||||||
|
|
||||||
|
// if return null, the event won't be sent
|
||||||
|
// ref https://github.com/getsentry/sentry-javascript/issues/2039
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
dsn: sentryDataSourceName,
|
||||||
|
integrations: [
|
||||||
|
new Dedupe(),
|
||||||
|
new Offline(),
|
||||||
|
],
|
||||||
|
initialScope: {
|
||||||
|
tags: {
|
||||||
|
"process": mapProcessName(process.type),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
environment: isProduction ? "production" : "development",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default initializeSentryReportingWithInjectable;
|
||||||
@ -29,7 +29,7 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
this.listeners.length = 0;
|
this.listeners.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit = (...data: D) => {
|
emit(...data: D) {
|
||||||
for (const [callback, { once }] of this.listeners) {
|
for (const [callback, { once }] of this.listeners) {
|
||||||
if (once) {
|
if (once) {
|
||||||
this.removeListener(callback);
|
this.removeListener(callback);
|
||||||
@ -39,5 +39,5 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/common/fetch/download-binary.injectable.ts
Normal file
56
src/common/fetch/download-binary.injectable.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { RequestInit, Response } from "node-fetch";
|
||||||
|
import type { AsyncResult } from "../utils/async-result";
|
||||||
|
import fetchInjectable from "./fetch.injectable";
|
||||||
|
|
||||||
|
export interface DownloadBinaryOptions {
|
||||||
|
signal?: AbortSignal | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DownloadBinary = (url: string, opts?: DownloadBinaryOptions) => Promise<AsyncResult<Buffer, string>>;
|
||||||
|
|
||||||
|
const downloadBinaryInjectable = getInjectable({
|
||||||
|
id: "download-binary",
|
||||||
|
instantiate: (di): DownloadBinary => {
|
||||||
|
const fetch = di.inject(fetchInjectable);
|
||||||
|
|
||||||
|
return async (url, opts) => {
|
||||||
|
let result: Response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: upgrade node-fetch once we switch to ESM
|
||||||
|
result = await fetch(url, opts as RequestInit);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status < 200 || 300 <= result.status) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: result.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: true,
|
||||||
|
response: await result.buffer(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default downloadBinaryInjectable;
|
||||||
55
src/common/fetch/download-json.injectable.ts
Normal file
55
src/common/fetch/download-json.injectable.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { RequestInit, Response } from "node-fetch";
|
||||||
|
import type { AsyncResult } from "../utils/async-result";
|
||||||
|
import fetchInjectable from "./fetch.injectable";
|
||||||
|
|
||||||
|
export interface DownloadJsonOptions {
|
||||||
|
signal?: AbortSignal | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise<AsyncResult<unknown, string>>;
|
||||||
|
|
||||||
|
const downloadJsonInjectable = getInjectable({
|
||||||
|
id: "download-json",
|
||||||
|
instantiate: (di): DownloadJson => {
|
||||||
|
const fetch = di.inject(fetchInjectable);
|
||||||
|
|
||||||
|
return async (url, opts) => {
|
||||||
|
let result: Response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await fetch(url, opts as RequestInit);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.status < 200 || 300 <= result.status) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: result.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: true,
|
||||||
|
response: await result.json(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
callWasSuccessful: false,
|
||||||
|
error: String(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default downloadJsonInjectable;
|
||||||
9
src/common/fetch/fetch.global-override-for-injectable.ts
Normal file
9
src/common/fetch/fetch.global-override-for-injectable.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function";
|
||||||
|
import fetchInjectable from "./fetch.injectable";
|
||||||
|
|
||||||
|
export default getGlobalOverrideForFunction(fetchInjectable);
|
||||||
21
src/common/fetch/fetch.injectable.ts
Normal file
21
src/common/fetch/fetch.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type * as FetchModule from "node-fetch";
|
||||||
|
|
||||||
|
const { NodeFetch: { default: fetch }} = require("../../../build/webpack/node-fetch.bundle") as { NodeFetch: typeof FetchModule };
|
||||||
|
|
||||||
|
type Response = FetchModule.Response;
|
||||||
|
type RequestInit = FetchModule.RequestInit;
|
||||||
|
|
||||||
|
export type Fetch = (url: string, init?: RequestInit) => Promise<Response>;
|
||||||
|
|
||||||
|
const fetchInjectable = getInjectable({
|
||||||
|
id: "fetch",
|
||||||
|
instantiate: (): Fetch => fetch,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default fetchInjectable;
|
||||||
17
src/common/fetch/timeout-controller.ts
Normal file
17
src/common/fetch/timeout-controller.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an AbortController with an associated timeout
|
||||||
|
* @param timeout The number of milliseconds before this controller will auto abort
|
||||||
|
*/
|
||||||
|
export function withTimeout(timeout: number): AbortController {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
controller.signal.addEventListener("abort", () => clearTimeout(id));
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
|
|
||||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
|
||||||
|
|
||||||
export type AppNavigationChannel = MessageChannel<string>;
|
|
||||||
|
|
||||||
const appNavigationChannelInjectable = getInjectable({
|
|
||||||
id: "app-navigation-channel",
|
|
||||||
|
|
||||||
instantiate: (): AppNavigationChannel => ({
|
|
||||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default appNavigationChannelInjectable;
|
|
||||||
12
src/common/front-end-routing/app-navigation-channel.ts
Normal file
12
src/common/front-end-routing/app-navigation-channel.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||||
|
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export type AppNavigationChannel = MessageChannel<string>;
|
||||||
|
|
||||||
|
export const appNavigationChannel: AppNavigationChannel = {
|
||||||
|
id: IpcRendererNavigationEvents.NAVIGATE_IN_APP,
|
||||||
|
};
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
|
||||||
import type { MessageChannel } from "../utils/channel/message-channel-injection-token";
|
|
||||||
import { messageChannelInjectionToken } from "../utils/channel/message-channel-injection-token";
|
|
||||||
|
|
||||||
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
|
||||||
|
|
||||||
const clusterFrameNavigationChannelInjectable = getInjectable({
|
|
||||||
id: "cluster-frame-navigation-channel",
|
|
||||||
|
|
||||||
instantiate: (): ClusterFrameNavigationChannel => ({
|
|
||||||
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: messageChannelInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default clusterFrameNavigationChannelInjectable;
|
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { IpcRendererNavigationEvents } from "../../renderer/navigation/events";
|
||||||
|
import type { MessageChannel } from "../utils/channel/message-channel-listener-injection-token";
|
||||||
|
|
||||||
|
export type ClusterFrameNavigationChannel = MessageChannel<string>;
|
||||||
|
|
||||||
|
export const clusterFrameNavigationChannel: ClusterFrameNavigationChannel = {
|
||||||
|
id: IpcRendererNavigationEvents.NAVIGATE_IN_CLUSTER,
|
||||||
|
};
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable";
|
||||||
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
|
const leasesRouteInjectable = getInjectable({
|
||||||
|
id: "leases",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const isAllowedResource = di.inject(isAllowedResourceInjectable, "leases");
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: "/leases",
|
||||||
|
clusterFrame: true,
|
||||||
|
isEnabled: isAllowedResource,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: frontEndRouteInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default leasesRouteInjectable;
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import leasesRouteInjectable from "./leases-route.injectable";
|
||||||
|
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
|
||||||
|
|
||||||
|
const navigateToLeasesInjectable = getInjectable({
|
||||||
|
id: "navigate-to-leases",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||||
|
const route = di.inject(leasesRouteInjectable);
|
||||||
|
|
||||||
|
return () => navigateToRoute(route);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default navigateToLeasesInjectable;
|
||||||
@ -3,18 +3,18 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import appPreferencesRouteInjectable from "./app-preferences-route.injectable";
|
import runtimeClassesRouteInjectable from "./runtime-classes-route.injectable";
|
||||||
import { navigateToRouteInjectionToken } from "../../../navigate-to-route-injection-token";
|
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
|
||||||
|
|
||||||
const navigateToAppPreferencesInjectable = getInjectable({
|
const navigateToRuntimeClassesInjectable = getInjectable({
|
||||||
id: "navigate-to-app-preferences",
|
id: "navigate-to-runtime-classes",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||||
const route = di.inject(appPreferencesRouteInjectable);
|
const route = di.inject(runtimeClassesRouteInjectable);
|
||||||
|
|
||||||
return () => navigateToRoute(route);
|
return () => navigateToRoute(route);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default navigateToAppPreferencesInjectable;
|
export default navigateToRuntimeClassesInjectable;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable";
|
||||||
|
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
|
||||||
|
|
||||||
|
const runtimeClassesRouteInjectable = getInjectable({
|
||||||
|
id: "runtime-classes-route",
|
||||||
|
|
||||||
|
instantiate: (di) => {
|
||||||
|
const isAllowedResource = di.inject(isAllowedResourceInjectable, "runtimeclasses");
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: "/runtimeclasses",
|
||||||
|
clusterFrame: true,
|
||||||
|
isEnabled: isAllowedResource,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
injectionToken: frontEndRouteInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default runtimeClassesRouteInjectable;
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import { computed } from "mobx";
|
|
||||||
import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token";
|
|
||||||
|
|
||||||
const editorPreferencesRouteInjectable = getInjectable({
|
|
||||||
id: "editor-preferences-route",
|
|
||||||
|
|
||||||
instantiate: () => ({
|
|
||||||
path: "/preferences/editor",
|
|
||||||
clusterFrame: false,
|
|
||||||
isEnabled: computed(() => true),
|
|
||||||
}),
|
|
||||||
|
|
||||||
injectionToken: frontEndRouteInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default editorPreferencesRouteInjectable;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user