Compare commits

..
2 Commits
2982 changed files with 63661 additions and 188294 deletions

No files matched your search

-22
View File
@@ -1,22 +0,0 @@
schema_version = 1
project {
license = "BUSL-1.1"
copyright_year = 2024
# (OPTIONAL) A list of globs that should not have copyright/license headers.
# Supports doublestar glob patterns for more flexibility in defining which
# files or folders should be ignored
header_ignore = [
".github/**",
".husky/**",
"**/node_modules/**",
"**/dist/**",
"**/coverage/**",
"**/pnpm-lock.yaml",
"**/pnpm-workspace.yaml",
"ui/desktop/electron-app/out/**",
"ui/desktop/electron-app/ember-dist/**",
"e2e-tests/api-client/**"
]
}
-2
View File
@@ -1,2 +0,0 @@
# Anyone attached to this rule will always been assigned to a pull request
* @hashicorp/boundary-ui
+12 -48
View File
@@ -11,57 +11,21 @@ assignees: ''
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose this by contacting us at security@hashicorp.com. Our PGP key is available on [our security page](https://www.hashicorp.com/security/).
Logging is available on Boundary Desktop Client version >= 2.1.0
Logs are found at:
Linux: ~/.config/Boundary/logs/desktop-client.log
MacOS: ~/Library/Logs/Boundary/desktop-client.log
Windows: $env:USERPROFILE\AppData\Roaming\Boundary\logs\desktop-client.log
If requests are taking longer than expected, please change the logging level to DEBUG. This will log the cache daemon request times as well.
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose by contacting us at security@hashicorp.com. Our PGP key is available at [our security page](https://www.hashicorp.com/security/).
-->
## :bug: Bug Report
**Describe the bug**
A clear and concise description of what the bug is.
### **Description**
**To Reproduce**
Steps to reproduce the behavior:
1. Run `boundary authenticate ...`
2. Run `boundary connect ...`
3. See error
_A clear and concise description of the bug._
**Expected behavior**
A clear and concise description of what you expected to happen.
### **Steps to Reproduce**
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See the error
## **Expected behavior**
_A clear description of what you expected to happen._
## **Actual behavior**
_A clear description of what actually happened._
## **Screenshots (if applicable)**
_Add screenshots or screen recordings to help explain the issue._
## **Environment**
- **OS and Version:** (e.g., Windows 10 (22H2), macOS (14.7.2))
- **Architecture:** (e.g., arm64, x86_64)
- **Browser/Version:** (if applicable)
- **Desktop Client Version:** (if applicable)
- **Controller/worker versions:** (if applicable)
## **Logs & Console Output (if applicable)**
_Paste any relevant error logs or browser console output here._
## **Additional Context**
_Any other details that might be relevant._
**Additional context**
Add any other context about the problem here.
-3
View File
@@ -1,6 +1,3 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
contact_links:
- name: Ask a question
url: https://discuss.hashicorp.com/c/boundary
-6
View File
@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
+7 -17
View File
@@ -1,30 +1,20 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
addons:
- changed-files:
- any-glob-to-any-file: "addons/**/*"
- addons/**/*
api-addon:
- changed-files:
- any-glob-to-any-file: "addons/api/**/*"
- addons/api/**/*
auth-addon:
- changed-files:
- any-glob-to-any-file: "addons/auth/**/*"
- addons/auth/**/*
styles-addon:
- changed-files:
- any-glob-to-any-file: "addons/rose/**/*"
- addons/rose/**/*
ui:
- changed-files:
- any-glob-to-any-file: "ui/**/*"
- ui/**/*
admin:
- changed-files:
- any-glob-to-any-file: "ui/admin/**/*"
- ui/admin/**/*
desktop:
- changed-files:
- any-glob-to-any-file: "ui/desktop/**/*"
- ui/desktop/**/*
-33
View File
@@ -1,33 +0,0 @@
# Description
<!-- Add a brief description of changes here -->
<!-- Uncomment line below to manually add link to JIRA ticket -->
<!-- :tickets: [Jira ticket](https://hashicorp.atlassian.net/browse/JIRA_TICKET_NUMBER) -->
## Screenshots (if appropriate)
## How to Test
<!-- Add steps to test this change. Include any other necessary relevant links -->
## Checklist
<!-- strikethrough the checklist item that is not relevant to your change -->
<!-- If you are merging a long lived branch, major feature, or UI altering changes,
please add the a11y-tests label. Other cases, like a dependency change or
translation update likely does not need an a11y audit -->
- [ ] I have added before and after screenshots for UI changes
- [ ] I have added JSON response output for API changes
- [ ] I have added steps to reproduce and test for bug fixes in the description
- [ ] I have commented on my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added `a11y-tests` label to run a11y audit tests if needed
## PCI review checklist
<!-- heimdall_github_prtemplate:grc-pci_dss-2024-01-05 -->
- [ ] I have documented a clear reason for, and description of, the change I am making.
- [ ] If applicable, I've documented a plan to revert these changes if they require more than reverting the pull request.
- [ ] If applicable, I've documented the impact of any changes to security controls.
Examples of changes to security controls include using new access control methods, adding or removing logging pipelines, etc.
-94
View File
@@ -1,94 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
set -euo pipefail
# Configure Git
git config --global user.email "hc-github-team-secure-boundary@hashicorp.com"
git config --global user.name "github-team-secure-boundary"
# Fetch branches from ENT
git fetch origin
BRANCH=${1}
echo "BRANCH to merge is $BRANCH"
git checkout "${BRANCH}"
# Checkout OSS repo
git remote add oss-remote https://github.com/hashicorp/boundary-ui.git
git fetch oss-remote
# Merge oss/${BRANCH} into ent/${BRANCH}
git_merge_branch="${2}"
git checkout -b "${git_merge_branch}"
latest_oss_commit="$(git rev-parse --short oss-remote/${BRANCH})"
echo "Trying to merge the latest oss commit - ${latest_oss_commit} from oss/${BRANCH} into ent/${BRANCH}"
if ! errors=$(git merge -m "Merge Boundary UI OSS branch '${BRANCH}' at commit ${latest_oss_commit}" "${latest_oss_commit}"); then
# Here we can try to auto resolve conflicts
# This logic will vary from project to project. The below is just an example.
conflicts=$(git diff --name-only --diff-filter=U)
printf "Found conflicts:\n${conflicts}\n"
# Resolve package.json conflicts by taking oss
if echo "$conflicts" | grep -q package.json; then
echo "Checking out oss package.json..."
git checkout --theirs package.json
git add package.json
fi
# Resolve pnpm-lock.yaml conflicts by re-running pnpm install:
# https://pnpm.io/git#merge-conflicts
if echo "$conflicts" | grep -q pnpm-lock.yaml; then
echo "Re-running pnpm install to fix conflicts by regenerating pnpm-lock.yaml..."
pnpm install
git add pnpm-lock.yaml
fi
# If we can commit all the changes without conflict, we have resolved everything automatically
if ! git commit --no-edit; then
echo "Auto Merge failed, creating a draft PR"
git merge --abort
git clean -xfd
git fetch oss-remote
git checkout "${OSS_BRANCH}"
git checkout -b "${OSS_LOCAL_BRANCH}"
git push -f --set-upstream origin "${OSS_LOCAL_BRANCH}"
gh pr create \
--draft \
--fill-first \
--label "oss-merge" \
-B "${BASE_BRANCH}" \
-H "${OSS_LOCAL_BRANCH}" \
-a "${ACTOR}" \
-t "[${BASE_BRANCH/oss\/release\//}] OSS to ENT merge of (${SHA::7})" \
-b "${BASE_BRANCH/oss\//} upstream merge of (${SHA::7})
Merge failed, Please fix the conflicts using github UI or using the following commands:
\`\`\`bash
git checkout ${BASE_BRANCH}
git pull
git checkout ${OSS_LOCAL_BRANCH}
git merge ${BASE_BRANCH}
# fix conflicts and commit
git push origin ${OSS_LOCAL_BRANCH}
\`\`\`"
exit 0
fi
fi
# Check that there are actually changes to merge by comparing the
# merge branch to the current checked out branch
if (git diff ${BRANCH} --exit-code > /dev/null); then
echo "there are no changes to merge"
exit 0
fi
git push origin "${git_merge_branch}"
gh pr create \
--fill-first \
--label "oss-merge" \
-B "${BASE_BRANCH}" \
-H "${MERGE_BRANCH}" \
-a ${ACTOR} \
-t "[${BASE_BRANCH/oss\/release\//}] OSS to ENT merge of (${SHA::7})" \
-b "${BASE_BRANCH/oss\//} upstream merge of (${SHA::7})"
gh pr merge --auto --merge
# This PR will be need to be approved manually
-56
View File
@@ -1,56 +0,0 @@
name: A11y Tests
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
workflow_dispatch:
jobs:
a11y-test:
name: Run A11y Tests
if: contains(github.event.pull_request.labels.*.name, 'a11y-tests')
runs-on: ${{ fromJSON(vars.RUNNER) }}
timeout-minutes: 40
steps:
- uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0
with:
install-dependencies: true
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install
- name: Run a11y tests
env:
# this will generate the test reports but causes tests to pass even when there a11y failures
ENABLE_A11Y_MIDDLEWARE_REPORTER: true
run: pnpm test-a11y
- name: Consolidate ember a11y reports
run: |
mkdir ember-a11y-reports
jq -s 'map(.[])' ./ui/admin/ember-a11y-report/*.json > ember-a11y-reports/admin.json
jq -s 'map(.[])' ./ui/desktop/ember-a11y-report/*.json > ember-a11y-reports/desktop.json
- name: Output ember a11y test report summary (admin)
run: 'cat ember-a11y-reports/admin.json | jq "map({moduleName,testName,routes,urls,violations: .violations | map({id, nodes: .nodes | map({failureSummary, targets: .[].[]?.relatedNodes?.[]?.target? })}) })"'
- name: Output ember a11y test report summary (desktop)
run: 'cat ember-a11y-reports/desktop.json | jq "map({moduleName,testName,routes,urls,violations: .violations | map({id, nodes: .nodes | map({failureSummary, targets: .[].[]?.relatedNodes?.[]?.target? })}) })"'
- name: Upload ember a11y reports
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ember-a11y-reports
path: ./ember-a11y-reports
- name: Report a11y test results status
if: always()
# fail CI when the combined array length of admin and desktop a11y reports is not 0
run: |
EMBER_A11Y_FAILURE_COUNT=$(jq -s 'map(.[]) | length' ember-a11y-reports/admin.json ember-a11y-reports/desktop.json)
echo "$EMBER_A11Y_FAILURE_COUNT ember-a11y test failures"
if [ "$EMBER_A11Y_FAILURE_COUNT" != '0' ]; then
exit 1
fi
-94
View File
@@ -1,94 +0,0 @@
name: Build Admin UI
on:
push:
branches:
- main
- "release/**"
workflow_dispatch:
jobs:
dependencies:
name: Install dependencies
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
build-admin-ui-oss:
name: Build Admin UI OSS
needs: [dependencies]
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- name: Build
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:oss
- name: Upload artifact admin-ui
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: admin-ui
path: ./ui/admin/dist/
- name: Upload artifact OSS admin UI
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: admin-ui-oss
path: ./ui/admin/dist/
build-admin-ui-enterprise:
name: Build Admin UI Enterprise
needs: [dependencies]
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- name: Build
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:enterprise
- name: Upload artifact enterprise admin UI
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: admin-ui-ent
path: ./ui/admin/dist/
build-admin-ui-hcp:
name: Build Admin UI HCP
needs: [dependencies]
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- name: Build
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run build:ui:admin:hcp
- name: Upload artifact HCP admin UI
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: admin-ui-hcp
path: ./ui/admin/dist/
+20 -6
View File
@@ -7,7 +7,7 @@ name: "CodeQL"
on:
push:
branches: [main]
branches: [main, release-v0.1.0]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
@@ -17,7 +17,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ${{ fromJSON(vars.RUNNER) }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -30,18 +30,32 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# 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.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -54,4 +68,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@70df9def86d22bf0ea4e7f8b956e7b92e7c1ea22 # codeql-bundle-v2.20.7
uses: github/codeql-action/analyze@v1
-19
View File
@@ -1,19 +0,0 @@
name: Add Copyright Headers
on: pull_request
jobs:
copywrite:
runs-on: ${{ fromJSON(vars.RUNNER) }}
permissions:
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# Ref and repository aren't normally needed, this is a workaround for a bug in auto commit
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3
- name: Add missing Copyright Headers
run: copywrite headers
- uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
commit_message: Add missing copyright headers
+27 -46
View File
@@ -11,7 +11,7 @@ name: Jira Sync
jobs:
sync:
runs-on: ${{ fromJSON(vars.RUNNER) }}
runs-on: ubuntu-latest
name: Jira sync
steps:
- name: Check if community user
@@ -19,33 +19,36 @@ jobs:
id: boundary-team-role
run: |
TEAM=boundary
ROLE="$(gh api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')"
ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')"
if [[ -n ${ROLE} ]]; then
echo "Actor ${{ github.actor }} is a ${TEAM} team member, skipping ticket creation"
else
echo "Actor ${{ github.actor }} is not a ${TEAM} team member"
fi
echo "role=${ROLE}" >> $GITHUB_OUTPUT
echo "::set-output name=role::${ROLE}"
env:
GITHUB_TOKEN: ${{ secrets.JIRA_SYNC_GITHUB_TOKEN }}
- name: Login
uses: atlassian/gajira-login@v2.0.0
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
- name: Set ticket type
if: github.event.action == 'opened' && !steps.boundary-team-role.outputs.role
id: set-ticket-type
run: |
if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then
echo "type=PR" >> $GITHUB_OUTPUT
echo "::set-output name=type::PR"
else
echo "type=ISS" >> $GITHUB_OUTPUT
echo "::set-output name=type::ISS"
fi
- name: Create ticket
if: github.event.action == 'opened' && !steps.boundary-team-role.outputs.role
uses: tomhjp/gh-action-jira-create@3ed1789cad3521292e591a7cfa703215ec1348bf # v0.2.1
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
uses: tomhjp/gh-action-jira-create@v0.1.0
with:
project: ICU
issuetype: "GH Issue"
@@ -58,50 +61,28 @@ jobs:
- name: Search
if: github.event.action != 'opened'
id: search
uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
uses: tomhjp/gh-action-jira-search@v0.2.1
with:
# cf[10089] is Issue Link custom field
jql: 'issuetype = "GH Issue" and cf[10089]="${{ github.event.issue.html_url || github.event.pull_request.html_url }}"'
- name: Sync comment
if: github.event.action == 'created' && steps.search.outputs.issue
uses: tomhjp/gh-action-jira-comment@6eb6b9ead70221916b6badd118c24535ed220bd9 # v0.2.0
env:
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
uses: atlassian/gajira-comment@v2.0.2
with:
issue: ${{ steps.search.outputs.issue }}
comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}"
- name: Transitions
id: transitions
if: steps.search.outputs.issue
run: |
if [[ "${{ github.event.action }}" == "closed" || "${{ github.event.action }}" == "deleted" ]]; then
echo "Closing ticket"
echo "name=Done" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event.action }}" == "reopened" ]]; then
echo "Reopening ticket"
echo "name=Reopen" >> "$GITHUB_OUTPUT"
fi
- name: Close ticket
if: (github.event.action == 'closed' || github.event.action == 'deleted') && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: Done
# Transition issue API reference: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-transitions-post
- name: Transition ticket
if: steps.transitions.outputs.name
run: |
transitions="$(curl --silent \
--url "${{ secrets.JIRA_BASE_URL }}rest/api/3/issue/${{ steps.search.outputs.issue }}/transitions" \
--user "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
--header "Accept: application/json")"
id="$(echo "${transitions}" | jq -r '.transitions[] | select(.name == "${{ steps.transitions.outputs.name }}") | .id')"
curl --silent \
--url "${{ secrets.JIRA_BASE_URL }}rest/api/3/issue/${{ steps.search.outputs.issue }}/transitions" \
--user "${{ secrets.JIRA_USER_EMAIL }}:${{ secrets.JIRA_API_TOKEN }}" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data "$(printf '{"transition": {"id": "%s"}}' "${id}")"
- name: Reopen ticket
if: github.event.action == 'reopened' && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: "To Do"
+2 -2
View File
@@ -4,8 +4,8 @@ on:
jobs:
triage:
runs-on: ${{ fromJSON(vars.RUNNER) }}
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
- uses: actions/labeler@main
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
+47 -44
View File
@@ -4,71 +4,74 @@ jobs:
dependencies:
name: Install dependencies
runs-on: ${{ fromJSON(vars.RUNNER) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v2
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
node-version: '16.x'
- run: yarn install
compliance:
name: License Compliance
needs: [dependencies]
runs-on: ${{ fromJSON(vars.RUNNER) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v2
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm run compliance:licenses
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn run compliance:licenses
lint:
name: Lint
needs: [dependencies, compliance]
runs-on: ${{ fromJSON(vars.RUNNER) }}
needs: [dependencies]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
node-modules-
- uses: actions/setup-node@v2
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm lint
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn lint
test:
name: Test
needs: [lint]
runs-on: ${{ fromJSON(vars.RUNNER) }}
needs: [dependencies]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0
- uses: actions/checkout@v2
- uses: actions/cache@v2
id: node-modules-cache
with:
install-dependencies: true
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
path: '**/node_modules'
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- uses: actions/setup-node@v2
with:
run_install: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'
- run: pnpm install --offline
- run: pnpm test
node-version: '16.x'
if: steps.node-modules-cache.outputs.cache-hit != 'true'
- run: yarn test
@@ -1,25 +0,0 @@
name: Trigger OSS to Enterprise Merge
on:
pull_request_target:
types:
- closed
branches:
- main
jobs:
trigger-oss-merge:
# Run this only on merge events in OSS repo
if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }}
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- name: Trigger Merge
env:
GIT_REF: ${{ github.ref_name }}
GIT_SHA: ${{ github.sha }}
GH_PAT: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
GIT_ACTOR: ${{ github.actor }}
run: |
curl -H "Authorization: token $GH_PAT" \
-H 'Accept: application/json' \
-d "{\"event_type\": \"oss-merge\", \"client_payload\": {\"git-ref\": \"${GIT_REF}\", \"git-sha\": \"${GIT_SHA}\", \"git-actor\": \"${GIT_ACTOR}\" }}" \
"https://api.github.com/repos/hashicorp/boundary-ui-enterprise/dispatches"
-24
View File
@@ -1,24 +0,0 @@
name: OSS → ENT Merge
on:
repository_dispatch:
types: [ oss-merge ]
jobs:
oss-merge:
runs-on: ${{ fromJSON(vars.RUNNER) }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- run: |
git fetch --prune --unshallow
./.github/scripts/merge.sh "${BASE_BRANCH}" "${MERGE_BRANCH}"
env:
BASE_BRANCH: ${{ github.event.client_payload.git-ref }}
OSS_BRANCH: oss-remote/${{ github.event.client_payload.git-ref }}
OSS_LOCAL_BRANCH: oss/${{ github.event.client_payload.git-ref }}-${{ github.event.client_payload.git-sha }}
MERGE_BRANCH: oss/merge-${{ github.event.client_payload.git-ref }}-${{github.event.client_payload.git-sha}}
GITHUB_TOKEN: ${{secrets.ELEVATED_GITHUB_TOKEN }}
MERGE_APPROVE_TOKEN: ${{secrets.ELEVATED_GITHUB_TOKEN }}
ACTOR: ${{ github.event.client_payload.git-actor }}
SHA: ${{github.event.client_payload.git-sha}}
-207
View File
@@ -1,207 +0,0 @@
name: Test E2E Admin UI
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
paths:
- 'ui/admin/**'
- 'e2e-tests/*'
- 'e2e-tests/api-client/**'
- 'e2e-tests/helpers/**'
- 'e2e-tests/admin/**'
- '.github/workflows/test-e2e-admin-ui.yaml'
workflow_dispatch:
inputs:
boundary-enterprise-branch:
description: 'Branch of boundary-enterprise to test against (default to main)'
required: true
default: 'main'
type: string
boundary-branch:
description: 'Branch of boundary to test against (default to main)'
required: true
default: 'main'
type: string
jobs:
test:
name: test
runs-on: ${{ fromJSON(vars.RUNNER_UBUNTU_22) }}
strategy:
matrix:
include:
- boundary_edition: community
github_repo: hashicorp/boundary
folder_name: boundary
boundary_edition_short: oss
enos_scenario: e2e_ui_docker
test_command: admin:ce:docker
boundary_branch: ${{ github.event.inputs.boundary-branch || 'main' }}
- boundary_edition: enterprise
github_repo: hashicorp/boundary-enterprise
folder_name: boundary-enterprise
boundary_edition_short: ent
enos_scenario: e2e_ui_docker_ent
test_command: admin:ent:docker
boundary_branch: ${{ github.event.inputs.boundary-enterprise-branch || 'main' }}
fail-fast: false
env:
ENOS_VAR_e2e_debug_no_run: true
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- name: Set up Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
ui/desktop/electron-app/pnpm-lock.yaml
- name: Set up Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
- name: Set up Enos
uses: hashicorp/action-setup-enos@39a8f388434bea5eeba6649b99aa9949a55a1150
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Vault CLI
run: |
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/vault.zip
unzip /tmp/vault.zip -d /usr/local/bin
- name: GPG setup
run: |
# Create a GPG key
export KEY_PW=boundary
gpg --generate-key --batch <<eoGpgConf
%echo Started!
Key-Type: RSA
Key-Length: default
Subkey-Type: RSA
Name-Real: boundary
Name-Comment: default
Name-Email: default
Expire-Date: 0
Passphrase: $KEY_PW
%commit
%echo Done.
eoGpgConf
# Enable gpg-preset-passphrase so that key passwords can be saved
echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf
gpg-connect-agent reloadagent /bye &>/dev/null
# Get information about the created keys
export lines=$(gpg --list-secret-keys --with-colons --with-keygrip)
export KEY_ID=""
while read -r line
do
# Save the first key id to be used later
if [[ $line =~ "fpr"* ]]; then
if [[ $KEY_ID == "" ]]; then
KEY_ID="$(echo "$line" | sed -r 's/fpr|://g')"
fi
fi
# Cache the passphrases for the keys so passwords do not need to be entered
if [[ $line =~ "grp"* ]]; then
export KEYGRIP_ID="$(echo "$line" | sed -r 's/grp|://g')"
/usr/lib/gnupg/gpg-preset-passphrase --preset -P $KEY_PW $KEYGRIP_ID
fi
done <<< $lines
# Trust the key
touch /tmp/test.txt
gpg -a --encrypt -r $KEY_ID --trust-model always --batch --yes /tmp/test.txt
echo "trusted-key $KEY_ID" >> ~/.gnupg/gpg.conf
# Initialize the password store
pass init $KEY_ID &>/dev/null
- name: Checkout ${{ matrix.folder_name }} repo
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: ${{ matrix.github_repo }}
path: support/src/${{ matrix.folder_name }}
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
ref: ${{ matrix.boundary_branch }}
- name: GH fix for localhost resolution
run: |
cat /etc/hosts && echo "-----------"
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
cat /etc/hosts
ssh -V
echo "127.0.0.1 localhost boundary" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost worker" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost vault" | sudo tee -a /etc/hosts
- name: Set git config
run: |
git config --global url."https://oauth2:${{ secrets.ELEVATED_GITHUB_TOKEN }}@github.com".insteadOf "https://github.com"
- name: Install node dependencies
run: |
pnpm install --frozen-lockfile
cd e2e-tests
pnpm playwright install --with-deps
- name: Set up test infra
id: infra
run: |
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
mkdir -p ./enos/terraform-plugin-cache
echo ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} > ./support/src/${{matrix.folder_name}}/internal/ui/VERSION
export ENOS_VAR_ui_build_override="oss" && \
export ENOS_VAR_boundary_edition=${{ matrix.boundary_edition_short }} && \
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
export ENOS_VAR_boundary_license="${{ secrets.BOUNDARY_ENT_LICENSE }}" && \
enos scenario launch --timeout 60m0s --chdir ./support/src/${{matrix.folder_name}}/enos ${{matrix.enos_scenario}} builder:local
- name: Move boundary binary
run: |
mv ./support/src/${{matrix.folder_name}}/bin/boundary /usr/local/bin/
boundary version
- name: Run Tests
run: |
source <(bash ./support/src/${{matrix.folder_name}}/enos/scripts/test_e2e_env.sh)
cd e2e-tests
pnpm run ${{ matrix.test_command }} --reporter=html
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: playwright-report-${{ matrix.boundary_edition }}
path: e2e-tests/playwright-report
- name: Clean up test infra
if: ${{ always() }}
run: |
export ENOS_VAR_boundary_edition=${{ matrix.boundary_edition_short }} && \
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
enos scenario destroy --timeout 60m0s --chdir ./support/src/${{matrix.folder_name}}/enos ${{matrix.enos_scenario}} builder:local
- name: Send Slack message on failure
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
if: ${{ failure() && github.repository == 'hashicorp/boundary-ui' }}
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: ":x: admin ui e2e tests failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Branch:* ${{ github.repository }}:${{ github.head_ref }}"
-128
View File
@@ -1,128 +0,0 @@
name: Test E2E Desktop Client
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
paths:
- 'ui/desktop/**'
- 'e2e-tests/*'
- 'e2e-tests/api-client/**'
- 'e2e-tests/helpers/**'
- 'e2e-tests/desktop/**'
- '.github/workflows/test-e2e-desktop.yaml'
workflow_dispatch:
jobs:
test:
name: test
runs-on: ${{ fromJSON(vars.RUNNER_UBUNTU_22) }}
env:
ENOS_VAR_boundary_edition: "ent"
ENOS_VAR_e2e_debug_no_run: true
SETUP_CLI: true
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 #v4.2.0
with:
run_install: false
- name: Set up Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 20
cache: "pnpm"
cache-dependency-path: |
pnpm-lock.yaml
ui/desktop/electron-app/pnpm-lock.yaml
- name: Set up Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
- name: Set up Enos
uses: hashicorp/action-setup-enos@39a8f388434bea5eeba6649b99aa9949a55a1150
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Vault CLI
run: |
wget https://releases.hashicorp.com/vault/1.12.2/vault_1.12.2_linux_amd64.zip -O /tmp/vault.zip
unzip /tmp/vault.zip -d /usr/local/bin
- name: Checkout boundary-enterprise
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: hashicorp/boundary-enterprise
# ref: ${{ github.event.inputs.boundary-enterprise-branch }}
path: support/src/boundary-enterprise
token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
- name: GH fix for localhost resolution
run: |
cat /etc/hosts && echo "-----------"
sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6 -localhost ip6-loopback/g' /etc/hosts
cat /etc/hosts
ssh -V
echo "127.0.0.1 localhost boundary" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost worker" | sudo tee -a /etc/hosts
echo "127.0.0.1 localhost vault" | sudo tee -a /etc/hosts
# TODO: Build image ourselves in case we don't want to use latest?
- name: Get Boundary docker image
run: |
docker pull hashicorp/boundary-enterprise:latest
docker save hashicorp/boundary-enterprise:latest | gzip > /tmp/boundary-enterprise.zip
- name: Set up test infra
id: infra
run: |
ssh-keygen -N '' -t ed25519 -f ~/.ssh/github_enos
mkdir -p ./enos/terraform-plugin-cache
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
export ENOS_VAR_boundary_license="${{ secrets.BOUNDARY_ENT_LICENSE }}" && \
export ENOS_VAR_boundary_docker_image_file=/tmp/boundary-enterprise.zip && \
enos scenario launch --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
- name: Install dependencies
run: |
pnpm install --frozen-lockfile
cd e2e-tests
pnpm playwright install --with-deps
# Runner is headless so we need to enable a display to run the client
- name: Run Tests
run: |
source <(bash ./support/src/boundary-enterprise/enos/scripts/test_e2e_env.sh)
cd e2e-tests
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
pnpm run desktop
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: playwright-report
path: e2e-tests/playwright-report
- name: Clean up test infra
if: ${{ always() }}
run: |
export ENOS_VAR_enos_user=$GITHUB_ACTOR && \
export ENOS_VAR_aws_ssh_private_key_path=~/.ssh/github_enos && \
enos scenario destroy --timeout 60m0s --chdir ./support/src/boundary-enterprise/enos e2e_ui_docker_ent builder:crt
- name: Send Slack message on failure
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
if: ${{ failure() && github.repository == 'hashicorp/boundary-ui' }}
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: ":x: desktop client e2e tests failed: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Branch:* ${{ github.repository }}:${{ github.head_ref }}"
@@ -0,0 +1,23 @@
name: "Trigger Test Boundary UI release"
on:
workflow_dispatch:
inputs:
branch:
description: Please use tag-like format n.n.n-someword (1.111.11-beta)
required: true
jobs:
trigger-circleci-release:
runs-on: ubuntu-latest
steps:
- name: Trigger Release job
if: github.event_name == 'workflow_dispatch'
run: |
echo "Triggering Test release using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}"
curl -u ${{ secrets.CIRCLE_TOKEN }}: -X POST --header "Content-Type: application/json" \
-d '{ "branch": "main", "parameters": {"TAG": "'${{ github.event.inputs.branch }}'" }}' \
https://circleci.com/api/v2/project/github/hashicorp/boundary-ui-releases/pipeline
+9 -12
View File
@@ -1,4 +1,4 @@
name: "Trigger Boundary Desktop release"
name: "Trigger Boundary UI release"
on:
push:
@@ -6,17 +6,14 @@ on:
- '*'
jobs:
trigger-gha-release:
runs-on: ${{ fromJSON(vars.RUNNER) }}
trigger-circleci-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Release description
- uses: actions/checkout@v2
- name: Trigger Release job
run: |
echo "Triggering release with a TAG ${GITHUB_REF#refs/tags/}"
- name: Trigger Release workflow in boundary-desktop-releases
run: |
export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}"
gh workflow run release.yml \
--repo hashicorp/boundary-desktop-releases \
--ref main \
-f TAG="${GITHUB_REF#refs/tags/}"
curl -u ${{ secrets.CIRCLE_TOKEN }}: -X POST --header "Content-Type: application/json" \
-d '{ "branch": "main", "parameters": {"TAG": "'${GITHUB_REF#refs/tags/}'" }}' \
https://circleci.com/api/v2/project/github/hashicorp/boundary-ui-releases/pipeline
+3 -19
View File
@@ -19,23 +19,7 @@ coverage-*
libpeerconnection.log
npm-debug.log*
testem.log
yarn-error.log
storybook-static
**/.storybook/*.html
.watchman-cookie*
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
-2
View File
@@ -1,2 +0,0 @@
pnpm compliance:licenses
pnpm lint-staged
-23
View File
@@ -1,23 +0,0 @@
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
# Gets the current branch name but if we're in a detached head state, return "HEAD"
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
# Regex to match the ICU-#### branch name pattern we use
ISSUE_REGEX="^(ICU-[0-9]+)"
# Don't run commitizen for merge or squash commits, as well as when rebasing
if [ "${COMMIT_SOURCE}" = merge ] || [ "${COMMIT_SOURCE}" = squash ] || [ "${COMMIT_SOURCE}" = "message" -a "${BRANCH_NAME}" = "HEAD" ];then
exit 0
fi
# If the branch name matches, set the JIRA link automatically
if [[ $BRANCH_NAME =~ $ISSUE_REGEX ]]
then
issue="${BASH_REMATCH[1]}"
url="https://hashicorp.atlassian.net/browse/${issue}"
exec < /dev/tty && npx git-cz --issues="$url" --hook || true
else
# This is to allow interaction with the terminal for commitizen
exec < /dev/tty && npx git-cz --hook || true
fi
-6
View File
@@ -1,6 +0,0 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
url_license = "https://github.com/hashicorp/boundary-ui/blob/main/LICENSE"
url_project_website = "https://learn.hashicorp.com/tutorials/boundary/getting-started-desktop-app"
url_source_repository = "https://github.com/hashicorp/boundary-ui"
+343 -72
View File
@@ -1,91 +1,362 @@
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
"Business Source License" is a trademark of MariaDB Corporation Ab.
Mozilla Public License, version 2.0
Parameters
1. Definitions
Licensor: HashiCorp, Inc.
Licensed Work: The Licensed Work is (c) 2024 HashiCorp, Inc.
Additional Use Grant: You may make production use of the Licensed Work, provided
Your use does not include offering the Licensed Work to third
parties on a hosted or embedded basis in order to compete with
HashiCorp's paid version(s) of the Licensed Work. For purposes
of this license:
1.1. "Contributor"
A "competitive offering" is a Product that is offered to third
parties on a paid basis, including through paid support
arrangements, that significantly overlaps with the capabilities
of HashiCorp's paid version(s) of the Licensed Work. If Your
Product is not a competitive offering when You first make it
generally available, it will not become a competitive offering
later due to HashiCorp releasing a new version of the Licensed
Work with additional capabilities. In addition, Products that
are not provided on a paid basis are not competitive.
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
"Product" means software that is offered to end users to manage
in their own environments or offered as a service on a hosted
basis.
1.2. "Contributor Version"
"Embedded" means including the source code or executable code
from the Licensed Work in a competitive offering. "Embedded"
also means packaging the competitive offering in such a way
that the Licensed Work must be accessed or downloaded for the
competitive offering to operate.
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
Hosting or using the Licensed Work(s) for internal purposes
within an organization is not considered a competitive
offering. HashiCorp considers your organization to include all
of your affiliates under common control.
1.3. "Contribution"
For binding interpretive guidance on using HashiCorp products
under the Business Source License, please visit our FAQ.
(https://www.hashicorp.com/license-faq)
Change Date: Four years from the date the Licensed Work is published.
Change License: MPL 2.0
means Covered Software of a particular Contributor.
For information about alternative licensing arrangements for the Licensed Work,
please contact licensing@hashicorp.com.
1.4. "Covered Software"
Notice
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
Business Source License 1.1
1.5. "Incompatible With Secondary Licenses"
means
Terms
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited production use.
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
1.6. "Executable Form"
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
means any form of the work other than Source Code Form.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
1.7. "Larger Work"
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
1.8. "License"
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
means this document.
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+56 -103
View File
@@ -13,95 +13,62 @@ subproject, e.g. running or testing.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Boundary UI Monorepo](#boundary-ui-monorepo)
- [Projects](#projects)
- [Core (addons/core):](#core-addonscore)
- [API (addons/api):](#api-addonsapi)
- [Auth (addons/auth):](#auth-addonsauth)
- [Rose (addons/rose)](#rose-addonsrose)
- [Admin (ui/admin)](#admin-uiadmin)
- [Desktop Client (ui/desktop)](#desktop-client-uidesktop)
- [Prerequisites](#prerequisites)
- [Optional Tooling](#optional-tooling)
- [Installation](#installation)
- [Pnpm Commands](#pnpm-commands)
- [Contributing](#contributing)
- [Branching](#branching)
- [Building ToC](#building-toc)
- [Building Admin UI for Production](#building-admin-ui-for-production)
- [Building with a Container](#building-with-a-container)
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
- [For Admin](#for-admin)
- [For Desktop](#for-desktop)
- [Committing](#committing)
- [License Checking](#license-checking)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Yarn Commands](#yarn-commands)
- [Contributing](#contributing)
- [Branching](#branching)
- [Building ToC](#building-toc)
- [Building Admin UI for Production](#building-admin-ui-for-production)
- [Building with a Container](#building-with-a-container)
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
- [For admin](#for-admin)
- [For desktop](#for-desktop)
- [Committing](#committing)
- [License Checking](#license-checking)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Projects
This monorepo contains multiple projects.
### Core (addons/core):
Core features use by Boundary UI's, i.e i18n, helpers or components. [Project Readme](addons/core).
### API (addons/api):
The API data access layer for Boundary and all the related mocks. [Project Readme](addons/api).
### Auth (addons/auth):
Authentication layer for Boundary. [Project Readme](addons/auth).
### Rose (addons/rose)
Common styles and components shared by Boundary UIs. [Project Readme](addons/rose).
### Admin (ui/admin)
The Admin UI for Boundary. [Project Readme](ui/admin).
### Desktop Client (ui/desktop)
The desktop client UI for Boundary. [Project Readme](ui/desktop).
## Prerequisites
You will need the following things properly installed on your computer.
* [Git][git]
* [Node.js][node]
* Supported versions: `v20` and `v22`.
* [Pnpm][pnpm] installed globally
* v10–v15
* **Apple Silicon Users**: if you experience problems building or running
Boundary UIs, try Node v15.
* [Yarn][yarn] installed globally
* [Google Chrome][chrome]
* [Firefox][firefox]
## Optional Tooling
Our team finds the following applications useful in day-to-day development
workflows. These are merely recommendations. We encourage you to use the tools
that best suit you and your personal style.
* [VS Code][vscode]: Code editor for building and debugging web applications
* [Warp][warp]: Terminal application for macOS
* [Homebrew][homebrew]: The missing package manager for macOS and Linux
* [Ember Inspector][ember-inspector]: Understand and debug Ember applications
* [Postman][postman]: Test and inspect APIs
[git]: https://git-scm.com/
[node]: https://nodejs.org/
[yarn]: https://classic.yarnpkg.com/lang/en/
[chrome]: https://google.com/chrome/
[firefox]: https://firefox.com/
[yarn-workspaces]: https://classic.yarnpkg.com/en/docs/workspaces/
## Installation
* `git clone <repository-url>` this repository
* `cd boundary-ui`
* `pnpm install`
* `yarn install`
## Pnpm Commands
## Yarn Commands
List of available project commands. `pnpm run <command-name>`
List of available project commands. `yarn run <command-name>`
| Command | Description |
|---------------------|---|
| build | Builds Admin UI for production. |
| build | Builds Admin UI. |
| test | Runs tests in CI mode. |
| commit | Replaces `git commit`, ensures compliance, audit, lint, and test checks pass before commit is allowed and normalizes commit messages across authors. |
| lint | Runs ember-template-lint on all of the hbs, js, and sass files. |
| compliance:audit | Checks for issues using `pnpm audit` with moderate and above criticality.|
| compliance:audit | Checks for issues using `yarn audit` with moderate and above criticality.|
| compliance:licenses | Checks that all dependencies have OSS-compatible licenses. |
| doc:toc | Automatically generates a table of contents for the README file. |
@@ -114,20 +81,20 @@ We follow [GitHub Flow][github-flow].
### Building ToC
To autogenerate a ToC (table of contents) for this README,
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
run `yarn doc:toc`. Please update the ToC whenever editing the structure
of README.
### Building Admin UI for Production
See [Admin UI README](ui/admin/README.md#building-for-production) for more information on how to
build the Admin UI for production. Be sure to set build-related environment variables as
necessary for your target environment, as described in the [Environment Variables](ui/admin/README.md#environment-variables) section of README.
See ui/admin/README.md Building for Production for more information on how to
build the Admin UI. Be sure to set build-related environment variables as
necessary for your target environment, as described in the Admin UI readme.
To quickly produce a production build of Admin UI, run from this folder:
```bash
pnpm install
pnpm build
yarn install
yarn build
```
Assets are saved to `ui/admin/dist/`.
@@ -144,27 +111,27 @@ Assets are saved to `ui/admin/dist/`.
### Building Desktop UI for Production
See [Desktop UI README](ui/desktop/README.md#building-for-production) for more information on how to
See ui/desktop/README.md Building for Production for more information on how to
build the Desktop UI. Be sure to set build-related environment variables as
necessary for your target environment, as described in the [Environment Variables](ui/desktop/README.md#environment-variables-prod) section of README.
necessary for your target environment, as described in the Desktop UI readme.
To quickly produce a production build of Desktop UI, run from this folder:
```bash
pnpm install
pnpm build:ui:desktop
yarn install
yarn build:ui:desktop
```
In windows, UI is generated using docker to provide a stable UI across platforms.
```cmd
pnpm install
docker-compose -f docker-compose-embedding.yml run build-desktop-production
pnpm build:ui:desktop:app
yarn install
docker-compose -f docker-compose-embedding.yml run build-desktop-production
yarn build:ui:desktop:app
```
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `pnpm build:ui:desktop:app`.
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn build:ui:desktop:app`.
Assets are saved to `ui/desktop/electron-app/out/make/`.
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
@@ -174,36 +141,34 @@ EXE archived desktop UI is available at asset location under `zip` folder.
This describes how to connect local Boundary UI to your local instance of Boundary.
This assumes you already have up and running a Boundary dev instance in your local environment with a listener `127.0.0.1:9200` (default behaviour). [Learn how to start a local Boundary dev instance](https://developer.hashicorp.com/boundary/tutorials/get-started-community/community-get-started-dev)
#### For Admin
This assumes you already have up and running a Boundary dev instance in your local environment with a listener `127.0.0.1:9200` (default behaviour). [Learn how to start a local Boundary dev instance](https://learn.hashicorp.com/tutorials/boundary/getting-started-dev?in=boundary/getting-started#start-a-dev-environment).
#### For admin
This assumes you are within `boundary-ui/ui/admin`.
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 pnpm start`.
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 yarn start`.
Following terminal instructions, open in your browser: `http://localhost:4200/`.
Once you open the UI you will see the login screen, authenticate using generated admin login name and password.
#### For Desktop
#### For desktop
This assumes you are within `boundary-ui/ui/desktop`.
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false pnpm start:desktop`.
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start:desktop`.
In login screen, authenticate using generated admin login name and password.
Enter the cluster URL of your Boundary dev instance, by default is: `http://localhost:9200`.
Enter the origin of your Boundary dev instance, by default is: `http://localhost:9200`.
### Committing
We use `husky` and `lint-staged` to ensure linting and other checks pass before a
commit is completed. Simply do a `pnpm install` to make sure the hooks are installed.
Use a normal `git commit` to go through the checks, if you need to skip these checks,
use `git commit --no-verify` to bypass them. However, a consistent commit message will
still be enforced even if you use `--no-verify`.
Instead of the default `git commit`, we prescribe a custom command that ensures
linting and other checks pass before a commit may be completed. It also ensures
that all contributors author consistent commit messages. To get started with a
commit, stage your files as usual. Then run `yarn commit` and follow the
on-screen instructions to author your message.
#### License Checking
@@ -215,15 +180,3 @@ The change will be verified upon PR. GPL variants and UNLICENSED dependencies
will not be accepted.
[github-flow]: https://guides.github.com/introduction/flow/
[git]: https://git-scm.com/
[node]: https://nodejs.org/
[pnpm]: https://pnpm.io
[chrome]: https://google.com/chrome/
[firefox]: https://firefox.com/
[pnpm-workspaces]: https://pnpm.io/workspaces
[homebrew]: https://brew.sh
[warp]: https://www.warp.dev
[vscode]: https://code.visualstudio.com/
[ember-inspector]: https://guides.emberjs.com/release/ember-inspector/
[postman]: https://www.postman.com/downloads/
-3
View File
@@ -12,8 +12,5 @@ insert_final_newline = true
indent_style = space
indent_size = 2
[*.hbs]
insert_final_newline = false
[*.{diff,md}]
trim_trailing_whitespace = false
+5 -3
View File
@@ -1,7 +1,9 @@
{
/**
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
Ember CLI sends analytics information by default. The data is completely
anonymous, but there are times when you might want to disable this behavior.
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
"isTypeScriptProject": false
"disableAnalytics": false
}
+7 -4
View File
@@ -1,14 +1,17 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/declarations/
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.*/
.eslintcache
# ember-try
/.node_modules.ember-try/
+27 -31
View File
@@ -1,32 +1,25 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
'use strict';
module.exports = {
root: true,
parser: '@babel/eslint-parser',
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 'latest',
ecmaVersion: 2018,
sourceType: 'module',
// This is specific babel-config. If grows consider creating a babel config file
requireConfigFile: false,
babelOptions: {
plugins: [
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
],
ecmaFeatures: {
legacyDecorators: true,
},
// end of babel-config
},
plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended', 'prettier'],
extends: [
'eslint:recommended',
'plugin:ember/recommended',
'plugin:prettier/recommended',
],
env: {
browser: true,
},
rules: {
curly: ['error', 'multi-line', 'consistent'],
'ember/no-get': 'off',
'ember/no-get-with-default': 'off',
'ember/no-computed-properties-in-native-classes': 'off',
@@ -37,16 +30,21 @@ module.exports = {
// node files
{
files: [
'./.eslintrc.js',
'./.prettierrc.js',
'./.template-lintrc.js',
'./ember-cli-build.js',
'./index.js',
'./testem.js',
'./blueprints/*/index.js',
'./config/**/*.js',
'./tests/dummy/config/**/*.js',
'./broccoli-plugins/**/*.js',
'.eslintrc.js',
'.prettierrc.js',
'.template-lintrc.js',
'ember-cli-build.js',
'index.js',
'testem.js',
'blueprints/*/index.js',
'config/**/*.js',
'tests/dummy/config/**/*.js',
],
excludedFiles: [
'addon/**',
'addon-test-support/**',
'app/**',
'tests/dummy/app/**',
],
parserOptions: {
sourceType: 'script',
@@ -55,15 +53,13 @@ module.exports = {
browser: false,
node: true,
},
extends: ['plugin:n/recommended'],
plugins: ['node'],
extends: ['plugin:node/recommended'],
},
{
// test files
// Test files:
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
rules: {
'qunit/require-expect': [2, 'except-simple'],
},
},
],
};
-20
View File
@@ -1,20 +0,0 @@
# compiled output
/dist/
/declarations/
# dependencies
/node_modules/
# misc
/.env*
/.pnp*
/.eslintcache
/coverage/
/testem.log
# ember-try
/.node_modules.ember-try/
/package.json.ember-try
# broccoli-debug
/DEBUG/
+7 -9
View File
@@ -2,7 +2,11 @@
/dist/
/tmp/
# dependencies
/bower_components/
# misc
/.bowerrc
/.editorconfig
/.ember-cli
/.env*
@@ -10,20 +14,14 @@
/.eslintignore
/.eslintrc.js
/.git/
/.github/
/.gitignore
/.prettierignore
/.prettierrc.js
/.template-lintrc.js
/.travis.yml
/.watchmanconfig
/bower.json
/CONTRIBUTING.md
/ember-cli-build.js
/testem.js
/tests/
/tsconfig.declarations.json
/tsconfig.json
/yarn.lock
.gitkeep
# ember-try
/.node_modules.ember-try/
/package.json.ember-try
+7 -4
View File
@@ -1,13 +1,16 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.*/
# ember-try
/.node_modules.ember-try/
.eslintcache
+4
View File
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"printWidth": 80
}
+1 -13
View File
@@ -1,17 +1,5 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
'use strict';
module.exports = {
overrides: [
{
files: '*.{js,ts}',
options: {
singleQuote: true,
},
},
],
singleQuote: true,
};
+1 -6
View File
@@ -1,10 +1,5 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
'use strict';
module.exports = {
extends: ['recommended', 'stylistic'],
extends: 'octane',
};
+1 -1
View File
@@ -1,3 +1,3 @@
{
"ignore_dirs": ["dist"]
"ignore_dirs": ["tmp", "dist"]
}
+14 -21
View File
@@ -8,7 +8,7 @@ This addon contains the API data access layer for Boundary.
- [Add API to an App](#add-api-to-an-app)
- [Installation](#installation)
- [Pnpm Commands](#pnpm-commands)
- [Yarn Commands](#yarn-commands)
- [Linting](#linting)
- [Formatting](#formatting)
- [Running tests](#running-tests)
@@ -21,7 +21,7 @@ This addon contains the API data access layer for Boundary.
## Add API to an App
Add this addon to an Ember application's `devDependencies` as:
`"api": "workspace:*"`, for applications included in this monorepo.
`"api": "*"`, for applications included in this monorepo.
Since this addon also includes Mirage mocks, be sure to install
`ember-cli-mirage` and add the following config to your UI project:
@@ -38,54 +38,47 @@ See monorepo README for installation instructions.
When manually installing addon, ensure `ember-data-fragments@5.0.0-beta.*` is installed to use api models with data fragments.
## Pnpm Commands
## Yarn Commands
List of available project commands. `pnpm run <command-name>`
List of available project commands. `yarn run <command-name>`
| Command | Description |
| ------- | ----------- |
| build:development | Builds the dummy app in development mode. |
| build | Builds the dummy app for production. |
| lint | Runs all lint commands. |
| lint:fix | Runs automatic lint fixes for all type of file. |
| lint:hbs | Runs lint for `hbs` template files. |
| lint:hbs:fix | Runs automatic lint fixes for `hbs` template files. |
| lint:js | Lints `js` files. |
| lint:js:fix | Runs automatic lint fixes for `js` files. |
| format | Runs all auto-formatters. |
| format:js | Auto-formats `js` files using Prettier. |
| start | Runs the dummy app local server. |
| test | Runs all tests. |
| test:ember-compatibility | Runs tests across multiple Ember versions with ember-try. |
| precommit | Runs all lint and format. |
| test:all | Runs tests across multiple Ember versions with ember-try. |
| doc:toc | Automatically generates a table of contents for this README file. |
Additional commands in the monorepo package may affect this projects.
## Linting
* `pnpm lint:fix`
* `pnpm lint:hbs`
* `pnpm lint:hbs:fix`
* `pnpm lint:js`
* `pnpm lint:js:fix`
* `yarn lint:hbs`
* `yarn lint:js`
* `yarn lint:js --fix`
## Formatting
Before submitting your work, be sure to run auto-formatters
(see commands above). This helps to ensure consistency among authors.
* `pnpm format`
* `yarn format`
## Running tests
* `pnpm test` – Runs the test suite on the current Ember version
* `pnpm test --server` – Runs the test suite in "watch mode"
* `pnpm test:all` – Runs the test suite against multiple Ember versions
* `yarn test` – Runs the test suite on the current Ember version
* `yarn test --server` – Runs the test suite in "watch mode"
* `yarn test:all` – Runs the test suite against multiple Ember versions
## Running the dummy application
* `pnpm start`
* `yarn start`
* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
## Contributing
@@ -95,5 +88,5 @@ See monorepo README for more contribution instructions.
### Building ToC
To autogenerate a ToC (table of contents) for this README,
run `pnpm doc:toc`. Please update the ToC whenever editing the structure
run `yarn doc:toc`. Please update the ToC whenever editing the structure
of README.
@@ -1,114 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import sinon from 'sinon';
import { typeOf } from '@ember/utils';
import { resourceNames } from 'api/handlers/cache-daemon-handler';
import { singularize } from 'ember-inflector';
import { underscore } from '@ember/string';
/**
* This test helper can be used to help setup your sinon stubs in your tests.
* Must be called after setupMirage.
*
* ```js
* import setupStubs from 'api/test-support/handlers/cache-daemon-search';
*
* module('Acceptance | my test', function(hooks) {
* setupApplicationTest(hooks);
* setupMirage(hooks);
* setupStubs(hooks);
*
* // add your actual tests here
* });
* ```
*
* This will setup a stub for the IPC service and include helper methods to
* help setup what the cache daemon search command returns.
*
* @module Test Helpers
* @public
*/
export default function setupStubs(hooks) {
let stubTypes = [];
hooks.beforeEach(function () {
const ipcService = this.owner.lookup('service:ipc');
this.ipcStub = sinon.stub(ipcService, 'invoke');
/**
* We aim to still use mirage data when stubbing out the cache daemon
* search. However, the handler expects the search data to be raw JSON from
* the API which will get serialized. We add back the scope object for it to
* get correctly normalized and other fields such as "attributes" from the
* API will already be hoisted and will not be re-normalized from mirage data.
*
* This convenience method can take in multiple types,
* e.g. this.stubCacheDaemonSearch('targets', 'sessions').
* This will have sinon stub out each call so the first call to search will
* provide all the models from the first parameter you specify,
* the second call will provide all the models from the second argument, etc.
* @param types
*/
this.stubCacheDaemonSearch = (...types) => {
stubTypes = types;
types.forEach((type, i) => {
this.ipcStub
.withArgs('searchCacheDaemon')
.onCall(i)
.callsFake(() => {
let models;
let resourceName;
if (typeOf(type) === 'object') {
models = type.func();
resourceName = type.resource;
} else {
models = this.server.schema[type].all().models;
resourceName = type;
}
return {
[underscore(resourceNames[singularize(resourceName)])]:
models.map((model) => {
// Use internal serializer to serialize the model correctly
// according to our mirage serializers
const modelData =
this.server.serializerOrRegistry.serialize(model);
// Serialize the data properly to standard JSON as that is what
// we're expecting from the cache daemon response
return JSON.parse(JSON.stringify(modelData));
}),
};
});
});
};
});
hooks.afterEach(function () {
// Verify the number of mocks we set up match with how
// many times the cache daemon was called with
sinon.assert.callCount(
this.ipcStub.withArgs('searchCacheDaemon'),
stubTypes.length,
);
stubTypes.forEach((type, i) => {
// Assert that the stub was called with the resource we're trying to mock
// so we don't mistakenly mock the wrong resource
const ipcCall = this.ipcStub.withArgs('searchCacheDaemon').getCall(i);
let resource = type;
if (typeOf(type) === 'object') {
resource = type.resource;
}
const mappedResourceName = resourceNames[singularize(resource)];
sinon.assert.match(ipcCall.args[1], { resource: mappedResourceName });
});
sinon.restore();
});
}
@@ -1,37 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { v4 as uuidv4 } from 'uuid';
/**
* This test helper can help set up and cleanup the database in your tests.
*
* ```js
* import { setupSqlite } from 'api/test-support/helpers/sqlite';
*
* module('Acceptance | my test', function(hooks) {
* setupApplicationTest(hooks);
* setupSqlite(hooks);
*
* // add your actual tests here
* });
* ```
*
* @param hooks
*/
export function setupSqlite(hooks) {
hooks.beforeEach(async function () {
const sqliteDb = this.owner.lookup('service:sqlite');
await sqliteDb.setup(`test-sqlite-${uuidv4()}`);
});
hooks.afterEach(async function () {
const sqliteDb = this.owner.lookup('service:sqlite');
await sqliteDb.deleteDatabase();
});
}
export default setupSqlite;
-29
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
@@ -11,30 +6,6 @@ import ModelAbility from './model';
export default class AccountAbility extends ModelAbility {
// =permissions
/**
* Only "known" account types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
/**
* Only "known" account types may be updated.
* @type {boolean}
*/
get canUpdate() {
return !this.model.isUnknown && this.hasAuthorizedAction('update');
}
/**
* Only "known" account types may be deleted.
* @type {boolean}
*/
get canDelete() {
return !this.model.isUnknown && this.hasAuthorizedAction('delete');
}
/**
* @type {boolean}
*/
-34
View File
@@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for auth method.
*/
export default class AuthMethodAbility extends ModelAbility {
// =permissions
/**
* Only "known" auth method types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
/**
* Only "known" auth method types may be updated.
*/
get canUpdate() {
return !this.model.isUnknown && this.hasAuthorizedAction('update');
}
/**
* Only "known" auth method types may be deleted.
*/
get canDelete() {
return !this.model.isUnknown && this.hasAuthorizedAction('delete');
}
}
@@ -1,26 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
import { service } from '@ember/service';
/**
* Provides abilities for channel recordings.
*/
export default class ChannelRecordingAbility extends ModelAbility {
// =services
@service abilities;
// =permissions
get canGetAsciicast() {
const sessionRecording = this.model.connection_recording.session_recording;
const sessionRecordingAbility = this.abilities.abilityFor(
'session-recording',
sessionRecording,
);
return this.model.isAsciicast && sessionRecordingAbility.canDownload;
}
}
@@ -1,20 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for credential.
*/
export default class CredentialLibraryAbility extends ModelAbility {
// =permissions
/**
* Only "known" credential library types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
}
@@ -1,27 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
import { service } from '@ember/service';
/**
* Provides abilities for credential store.
*/
export default class CredentialStoreAbility extends ModelAbility {
// =services
@service features;
// =permissions
/**
* Only "known" credential store types may be read.
* @type {boolean}
*/
get canRead() {
return this.features.isEnabled('static-credentials')
? this.hasAuthorizedAction('read')
: !this.model.isStatic && this.hasAuthorizedAction('read');
}
}
-20
View File
@@ -1,20 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for credential.
*/
export default class CredentialAbility extends ModelAbility {
// =permissions
/**
* Only "known" credential types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
}
-5
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
+1 -13
View File
@@ -1,14 +1,9 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for host sets.
*/
export default class HostSetAbility extends ModelAbility {
export default class UserAbility extends ModelAbility {
// =permissions
/**
@@ -24,11 +19,4 @@ export default class HostSetAbility extends ModelAbility {
get canRemoveHosts() {
return this.hasAuthorizedAction('remove-hosts');
}
/**
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && this.hasAuthorizedAction('read');
}
}
@@ -1,13 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for managed groups.
*/
export default class ManagedGroupAbility extends ModelAbility {
// no op
}
-5
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { Ability } from 'ember-can';
/**
-46
View File
@@ -1,46 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
import { service } from '@ember/service';
/**
* Provides abilities for storage policies.
*/
export default class PolicyAbility extends ModelAbility {
// =services
@service features;
// =permissions
/**
* This ensures that storage policies may be read only if the
* session-recording feature flag is enabled.
*/
get canRead() {
return this.features.isEnabled('ssh-session-recording')
? super.canRead
: false;
}
/**
* This ensures that storage policies may be updated only if the
* session-recording feature flag is enabled.
*/
get canUpdate() {
return this.features.isEnabled('ssh-session-recording')
? super.canUpdate
: false;
}
/**
* This ensures that storage policies may be deleted only if the
* session-recording feature flag is enabled.
*/
get canDelete() {
return this.features.isEnabled('ssh-session-recording')
? super.canDelete
: false;
}
}
-40
View File
@@ -1,23 +1,9 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
import { service } from '@ember/service';
class InvalidRolePrincipalTypeError extends Error {
name = 'InvalidRolePrincipalTypeError';
}
/**
* Provides abilities for roles.
*/
export default class RoleAbility extends ModelAbility {
// =services
@service can;
// =permissions
/**
@@ -27,13 +13,6 @@ export default class RoleAbility extends ModelAbility {
return this.hasAuthorizedAction('set-grants');
}
/**
* @type {boolean}
*/
get canSetGrantScopes() {
return this.hasAuthorizedAction('set-grant-scopes');
}
/**
* @type {boolean}
*/
@@ -47,23 +26,4 @@ export default class RoleAbility extends ModelAbility {
get canRemovePrincipals() {
return this.hasAuthorizedAction('remove-principals');
}
/**
* Returns the ability check for a principal model instance.
* Valid role principal models are: User, Group, and ManagedGroup.
* Throws an error if the principal type is invalid.
* @type {boolean}
* @throws {InvalidRolePrincipalTypeError}
*/
get canReadPrincipal() {
const type = this.model.constructor.modelName;
const typeIsValid =
type === 'user' || type === 'group' || type === 'managed-group';
if (!typeIsValid) {
throw new InvalidRolePrincipalTypeError(
`Expected a role principal of type 'user', 'group', or 'managed-group'. Got '${type}'.`,
);
}
return this.can.can(`read ${type}`, this.model);
}
}
-28
View File
@@ -1,28 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for scopes.
*/
export default class ScopeAbility extends ModelAbility {
// =permissions
/**
* Permission that checks whether a policy can be attached to a scope
* @type {boolean}
*/
get canAttachStoragePolicy() {
return this.hasAuthorizedAction('attach-storage-policy');
}
/**
* Permission that checks whether a policy can be detached to a scope
* @type {boolean}
*/
get canDetachStoragePolicy() {
return this.hasAuthorizedAction('detach-storage-policy');
}
}
@@ -1,42 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for session recordings.
*/
export default class SessionRecordingAbility extends ModelAbility {
// =services
// =permissions
/**
* Only "known" session recording types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && super.canRead;
}
/**
* Only "known" session recording types may be deleted.
* @type {boolean}
*/
get canDelete() {
return !this.model.isUnknown && super.canDelete && this.model.isAvailable;
}
get canDownload() {
return this.hasAuthorizedAction('download');
}
/* Permission that checks whether a policy can be reapplied to a session recording
* @type {boolean}
*/
get canReapplyStoragePolicy() {
return this.hasAuthorizedAction('reapply-storage-policy');
}
}
-38
View File
@@ -1,38 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for sessions.
*/
export default class SessionAbility extends ModelAbility {
// =services
// =permissions
/**
* Only available sessions may be read.
* @type {boolean}
*/
get canRead() {
return (
this.model.isAvailable &&
(this.hasAuthorizedAction('read:self') || super.canRead)
);
}
/**
* Only available sessions may be canceled.
* @type {boolean}
*/
get canCancel() {
return (
this.model.isAvailable &&
(this.hasAuthorizedAction('cancel:self') ||
this.hasAuthorizedAction('cancel'))
);
}
}
@@ -1,39 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for storage buckets.
*/
export default class StorageBucketAbility extends ModelAbility {
// =services
// =permissions
/**
* Only "known" storage bucket types may be read.
* @type {boolean}
*/
get canRead() {
return !this.model.isUnknown && super.canRead;
}
/**
* Only "known" storage bucket types may be updated.
* @type {boolean}
*/
get canUpdate() {
return !this.model.isUnknown && super.canUpdate;
}
/**
* Only "known" storage bucket types may be deleted.
* @type {boolean}
*/
get canDelete() {
return !this.model.isUnknown && super.canDelete;
}
}
+1 -15
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
@@ -35,19 +30,10 @@ export default class TargetAbility extends ModelAbility {
/**
* @type {boolean}
*/
get canAddBrokeredCredentialSources() {
get canAddCredentialSources() {
return this.hasAuthorizedAction('add-credential-sources');
}
/**
* @type {boolean}
*/
get canAddInjectedApplicationCredentialSources() {
return (
(this.model.isSSH || this.model.isRDP) &&
this.hasAuthorizedAction('add-credential-sources')
);
}
/**
* @type {boolean}
*/
-21
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
@@ -24,20 +19,4 @@ export default class UserAbility extends ModelAbility {
get canRemoveAccounts() {
return this.hasAuthorizedAction('remove-accounts');
}
/**
* @type {boolean}
*/
get canAddAccount() {
const { account, canAddAccounts } = this;
return canAddAccounts && !account.isUnknown;
}
/**
* @type {boolean}
*/
get canRemoveAccount() {
const { account, canRemoveAccounts } = this;
return canRemoveAccounts && !account.isUnknown;
}
}
-34
View File
@@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ModelAbility from './model';
/**
* Provides abilities for workers.
*/
export default class WorkerAbility extends ModelAbility {
// =permissions
/**
* @type {boolean}
*/
get canCreateWorkerLed() {
return this.hasAuthorizedCollectionAction('create:worker-led');
}
/**
* @type {boolean}
*/
get canSetWorkerTags() {
return this.hasAuthorizedAction('set-worker-tags');
}
/**
* @type {boolean}
*/
get canRemoveTags() {
return this.hasAuthorizedAction('remove-worker-tags');
}
}
+50 -59
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import RESTAdapter from '@ember-data/adapter/rest';
import { serializeIntoHash } from '@ember-data/adapter/-private';
/* eslint-disable-next-line ember/no-mixins */
@@ -45,7 +40,7 @@ function prenormalizeArrayResponse(response) {
}
export default class ApplicationAdapter extends RESTAdapter.extend(
AdapterBuildURLMixin,
AdapterBuildURLMixin
) {
// =attributes
@@ -108,43 +103,22 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
/**
* Intercepts "empty" responses and adds an empty `items` array.
* This query method now supports pagination by checking the responseType and
* passing in the list token if the responseType indicates more data
* @override
* @method findAll
* @return {Promise} promise
*/
findAll() {
return super.findAll(...arguments).then(prenormalizeArrayResponse);
}
/**
* Intercepts "empty" responses and adds an empty `items` array.
* @override
* @method query
* @return {Promise} promise
*/
async query(store, schema, query) {
const { batchLimit, ...queryObject } = query;
let result;
let data = [];
// If the query has a page_size, we skip the pagination logic
// and return the initial result. This is being used to determine
// if the controller supports pagination
if (query.page_size) {
result = await super.query(store, schema, query);
return prenormalizeArrayResponse(result);
}
// Run this loop as long as the response_type is delta,
// which indicates that there are more items in the list
// or the batch size has not been reached
do {
result = await super.query(store, schema, queryObject);
//add the result items to a data array
if (result && result.items) {
data.push(...result.items);
//pass in the list token for subsequent calls to fetch the remaining list items
queryObject.list_token = result.list_token;
}
} while (
result?.response_type === 'delta' &&
(!batchLimit || data.length < batchLimit)
);
result.items = data;
return prenormalizeArrayResponse(result);
query() {
return super.query(...arguments).then(prenormalizeArrayResponse);
}
/**
@@ -168,7 +142,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
}
/**
* Overrides default ajax method by rewriting PUT requests as PATCH.
* Overrides default ajax method by rewritting PUT requests as PATCH.
* PATCH is the request method used by our API for updates.
*
* @override
@@ -217,10 +191,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
handleResponse(status, headers, payload, requestData) {
if (this.isInvalid(status, headers, payload)) {
const detailedMessage = this.generatedDetailedMessage(...arguments);
const transformedPayload = this.transformValidationErrors(
payload,
status,
);
const transformedPayload = this.transformValidationErrors(payload);
return new InvalidError(transformedPayload.errors, detailedMessage);
}
return super.handleResponse(status, headers, payload, requestData);
@@ -278,30 +249,50 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
* error format.
*
* @param {object} payload
* @param status
* @return {object}
*/
transformValidationErrors(payload, status) {
transformValidationErrors(payload) {
const fieldErrors = get(payload, 'details.request_fields') || [];
// Normalize the primary error message into "base", which is an error
// that applies to the whole model instance
const baseError = {
isInvalid: true,
status,
code: payload?.kind,
detail: payload?.message,
status: payload.source,
code: payload.code,
detail: payload.message,
source: { pointer: '/data' },
};
// Normalize field-specific errors, if any.
const errors = fieldErrors.map((error) => ({
detail: error.description,
// Going forward, nested attributes will be hoisted. Thus "attributes."
// must be stripped from the error name key.
source: {
pointer: `/data/attributes/${error.name.replace('attributes.', '')}`,
},
}));
const errors = fieldErrors.flatMap((error) => {
const newValue = [
{
detail: error.description,
// Ember Data cannot handle errors on nested attributes, so these are
// flattened (e.g. `attributes.foobar` becomes `attributes_foobar`).
// To capture these errors, add read-only fields on the model
// corresponding to the underscored field name.
source: {
pointer: `/data/attributes/${error.name.replace('.', '_')}`,
},
},
];
if (error.name.includes('attributes.')) {
newValue.push({
detail: error.description,
// Going forward, nested attributes will be hoisted. Thus "attributes."
// must be stripped from the error name key. Until all models are
// transitioned away from model fragments, however, both this and the
// prior error key mappings are necessary.
source: {
pointer: `/data/attributes/${error.name.replace(
'attributes.',
''
)}`,
},
});
}
return newValue;
});
// Return a list of JSON API errors rooted under the `errors` key.
return {
errors: [baseError, ...errors],
@@ -322,6 +313,6 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
* @return {String} detailed error message
*/
generatedDetailedMessage(status, headers, payload /*, requestData*/) {
return payload?.message;
return payload.message;
}
}
+2 -11
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ApplicationAdapter from './application';
export default class HostCatalogAdapter extends ApplicationAdapter {
@@ -20,12 +15,8 @@ export default class HostCatalogAdapter extends ApplicationAdapter {
buildURL(modelName, id, snapshot, requestType) {
let url = super.buildURL(...arguments);
if (requestType === 'createRecord') {
try {
const pluginName = snapshot.attr('plugin')?.name;
if (pluginName) url = `${url}?plugin_name=${pluginName}`;
} catch (e) {
// Ignore any adapter errors here
}
const pluginName = snapshot.attr('plugin')?.name;
if (pluginName) url = `${url}?plugin_name=${pluginName}`;
}
return url;
}
@@ -1,74 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ApplicationAdapter from './application';
import { MIME_TYPE_ASCIICAST } from 'api/models/channel-recording';
export default class SessionRecordingAdapter extends ApplicationAdapter {
/**
* Overrides the default `buildURL` such that for custom download methods,
* URLs will get the query parameters. All other request types behave as normal.
*
* @param modelName {string}
* @param id {string}
* @param snapshot {object}
* @param requestType {string}
* @param query {object}
* @returns {string}
*/
buildURL(modelName, id, snapshot, requestType, query) {
let url = super.buildURL(...arguments);
let method;
try {
method = snapshot?.adapterOptions?.method;
} catch (e) {
// Ignore any adapter errors here
}
if (method === 'download') {
const queryParams = new URLSearchParams(query).toString();
if (queryParams) url = `${url}?${queryParams}`;
}
return url;
}
/**
* Handles the download recording for recordings. Takes in a mime type for the
* type or recording to return.
* The ID can be the session ID, session recording ID,
* connection recording ID, or the channel recording ID.
* @param id {string}
* @param mimeType {string}
* @returns {Promise<string>}
*/
async getRecordingDownload({ id, mimeType }) {
const options = { adapterOptions: { method: 'download' } };
const url = this.buildURL('session-recording', id, options, 'findRecord', {
mime_type: mimeType,
});
const response = await fetch(url, { headers: this.headers });
if (response?.ok) {
return response.text();
} else {
throw this.handleResponse(
response.status,
response.headers,
await response.text(),
);
}
}
/**
* Returns an ascii cast recording.
* @param id
* @returns {Promise<string>}
*/
getAsciicast(id) {
return this.getRecordingDownload({
id,
mimeType: MIME_TYPE_ASCIICAST,
});
}
}
@@ -1,33 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ApplicationAdapter from './application';
export default class StorageBucketAdapter extends ApplicationAdapter {
/**
* Overrides the default `buildURL` such that on _create_, URLs for records of
* type `plugin` get an extra query parameter `?plugin_name`. All other
* request types behave as normal.
*
* @override
* @param {string} modelName
* @param {string} id
* @return {string} url
* @return {string} requestType
*/
buildURL(modelName, id, snapshot, requestType) {
let url = super.buildURL(...arguments);
let pluginName;
try {
pluginName = snapshot?.attr('plugin')?.name;
} catch (e) {
// Ignore any adapter errors here
}
if (requestType === 'createRecord' && pluginName) {
url = `${url}?plugin_name=${pluginName}`;
}
return url;
}
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -48,39 +43,4 @@ export default class GeneratedAccountModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
// =attributes (password and LDAP)
@attr('string', {
for: ['password', 'ldap'],
isNestedAttribute: true,
description: 'The account login name',
})
login_name;
// =attributes (OIDC)
@attr('string', { for: 'oidc', isNestedAttribute: true }) subject;
@attr('string', { for: 'oidc', isNestedAttribute: true }) issuer;
// =attributes (OIDC and LDAP)
@attr('string', {
for: ['oidc', 'ldap'],
isNestedAttribute: true,
readOnly: true,
})
email;
@attr('string', {
for: ['oidc', 'ldap'],
isNestedAttribute: true,
readOnly: true,
})
full_name;
// =attributes (LDAP)
@attr('string', { for: 'ldap', isNestedAttribute: true, readOnly: true }) dn;
@attr('string-array', {
for: 'ldap',
isNestedAttribute: true,
readOnly: true,
})
member_of_groups;
}
@@ -1,64 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* Alias contains all fields related to an Alias resource
*/
export default class GeneratedAliasModel extends BaseModel {
// =attributes
@attr('string', {
description: 'The type of the resource, to help differentiate schemas',
})
type;
@attr('string', {
description: 'Optional name for identification purposes.',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes.',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description:
'This is the value referenced by the user that is resolved to the destination id.',
})
value;
@attr('string', {
description: 'This is the id of the resource that this Alias points to.',
})
destination_id;
@attr('object', {
isNestedAttribute: true,
description:
'This contains a host_id which is the id of the host that the session will be authorized for.',
})
authorize_session_arguments;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -54,267 +49,4 @@ export default class GeneratedAuthMethodModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
// =attributes(oidc, ldap)
@attr('string', {
for: ['oidc', 'ldap'],
readOnly: true,
isNestedAttribute: true,
})
state;
// =attributes(oidc)
@attr('string', {
for: 'oidc',
isNestedAttribute: true,
})
issuer;
@attr('string', {
for: 'oidc',
isNestedAttribute: true,
})
client_id;
@attr('string', {
for: 'oidc',
isNestedAttribute: true,
isSecret: true,
})
client_secret;
@attr('string', {
for: 'oidc',
readOnly: true,
isNestedAttribute: true,
})
client_secret_hmac;
@attr('number', {
for: 'oidc',
isNestedAttribute: true,
})
max_age;
@attr('string', {
for: 'oidc',
isNestedAttribute: true,
})
api_url_prefix;
@attr('string', {
for: 'oidc',
readOnly: true,
isNestedAttribute: true,
})
callback_url;
@attr('boolean', {
for: 'oidc',
isNestedAttribute: true,
})
disable_discovered_config_validation;
@attr('boolean', {
for: 'oidc',
isNestedAttribute: true,
})
dry_run;
@attr('string-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
claims_scopes;
@attr('string-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
signing_algorithms;
@attr('string-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
allowed_audiences;
@attr('string-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
idp_ca_certs;
@attr('account-value-map-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
account_claim_maps;
@attr('string-array', {
for: 'oidc',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
prompts;
// =attributes(ldap)
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
start_tls;
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
insecure_tls;
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
discover_dn;
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
anon_group_search;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
upn_domain;
@attr('string-array', {
for: 'ldap',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
urls;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
user_dn;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
user_attr;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
user_filter;
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
enable_groups;
@attr('boolean', {
for: 'ldap',
isNestedAttribute: true,
})
use_token_groups;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
group_dn;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
group_attr;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
group_filter;
@attr('string-array', {
for: 'ldap',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
certificates;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
client_certificate;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
isSecret: true,
})
client_certificate_key;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
readOnly: true,
})
client_certificate_key_hmac;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
bind_dn;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
isSecret: true,
})
bind_password;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
readOnly: true,
})
bind_password_hmac;
@attr('account-value-map-array', {
for: 'ldap',
emptyArrayIfMissing: true,
isNestedAttribute: true,
})
account_attribute_maps;
@attr('number', {
for: 'ldap',
isNestedAttribute: true,
})
maximum_page_size;
@attr('string', {
for: 'ldap',
isNestedAttribute: true,
})
dereference_aliases;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
/**
@@ -1,59 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A channel contains data for a single channel within a connection.
*/
export default class GeneratedChannelRecordingModel extends BaseModel {
// =attributes
@attr('string', {
description:
'The total number of bytes uploaded from the client in this Channel.',
readOnly: true,
})
bytes_up;
@attr('string', {
description:
'The total number of bytes downloaded to the client in this Channel.',
readOnly: true,
})
bytes_down;
@attr('number', {
description:
'The total number of errors that occurred during the use of this Channel.',
readOnly: true,
})
errors_number;
@attr('date', {
description: 'The time the Channel started.',
readOnly: true,
})
start_time;
@attr('date', {
description: 'The time the Channel finished.',
readOnly: true,
})
end_time;
@attr({
description: 'The array of mime types that this recording will support.',
readOnly: true,
})
mime_types;
@attr('duration', {
description: 'The duration of this channel recording in ms.',
readOnly: true,
})
duration;
}
@@ -1,66 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A connection contains data for a boundary connection within a session.
*/
export default class GeneratedConnectionRecordingModel extends BaseModel {
// =attributes
@attr('string', {
description:
'The ID of the Connection which this Connection Recording recorded.',
readOnly: true,
})
connection_id;
@attr('string', {
description:
'The total number of bytes uploaded from the client in this Connection.',
readOnly: true,
})
bytes_up;
@attr('string', {
description:
'The total number of bytes downloaded to the client in this Connection.',
readOnly: true,
})
bytes_down;
@attr('number', {
description:
'The total number of errors that occurred during the use of this Connection.',
readOnly: true,
})
errors_number;
@attr('date', {
description: 'The time the Connection started.',
readOnly: true,
})
start_time;
@attr('date', {
description: 'The time the Connection finished.',
readOnly: true,
})
end_time;
@attr({
description: 'The array of mime types that this recording will support.',
readOnly: true,
})
mime_types;
@attr('duration', {
description: 'The duration of this connection recording in ms.',
readOnly: true,
})
duration;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -49,95 +44,4 @@ export default class GeneratedCredentialLibraryModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
//= attributes (vault)
@attr('string', {
for: 'vault-generic',
isNestedAttribute: true,
description:
'The HTTP method the library uses when requesting credentials from Vault.',
defaultValue: () => 'GET',
})
http_method;
@attr('string', {
for: 'vault-generic',
isNestedAttribute: true,
description:
'The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.',
})
http_request_body;
@attr('string', {
isNestedAttribute: true,
description: 'The path in Vault to request credentials from.',
})
path;
@attr('string', {
for: 'vault-generic',
description: 'It indicates the type of credential the library returns.',
})
credential_type;
@attr('object', {
for: 'vault-generic',
description: 'It indicates the credential mapping overrides.',
emptyObjectIfMissing: true,
})
credential_mapping_overrides;
@attr('string', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description: 'The username to use when making an SSH connection.',
})
username;
@attr('string', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description: 'The desired key type to use when generating a private key.',
})
key_type;
@attr('number', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description:
'Specifies the number of bits used when generating the private key. Not used if key type is ed25519',
})
key_bits;
@attr('string', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description: 'Specifies the requested time to live for the certificate.',
})
ttl;
@attr('string', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description:
'Specifies the key id that the created certificate should have.',
})
key_id;
@attr('object-as-array', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description:
'Specifies a map of the critical options that the certificate should be signed for.',
})
critical_options;
@attr('object-as-array', {
for: 'vault-ssh-certificate',
isNestedAttribute: true,
description:
'Specifies a map of the extensions that the certificate should be signed for.',
})
extensions;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -44,91 +39,4 @@ export default class GeneratedCredentialStoreModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
// =attributes (vault)
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
})
address;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The namespace within Vault to use.',
})
namespace;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
})
ca_cert;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'Name to use as the SNI host when connecting to Vault via TLS.',
})
tls_server_name;
@attr('boolean', {
for: 'vault',
isNestedAttribute: true,
description: 'Whether or not to skip TLS verification.',
})
tls_skip_verify;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
isSecret: true,
description: 'A token used for accessing Vault.',
})
token;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The Vault token hmac.',
readOnly: true,
})
token_hmac;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'A PEM-encoded client certificate to use for TLS authentication to the Vault server.',
})
client_certificate;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
"A PEM-encoded private key matching the client certificate from 'client_certificate'.",
})
client_certificate_key;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description: 'The Vault client certificate key hmac.',
readOnly: true,
})
client_certificate_key_hmac;
@attr('string', {
for: 'vault',
isNestedAttribute: true,
description:
'Filters to the worker(s) who can handle Vault requests for this cred store.',
})
worker_filter;
}
@@ -1,103 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A credential is a data structure containing one or more secrets that binds an
* identity to a set of permissions or capabilities on a host for a session.
*/
export default class GeneratedCredentialModel extends BaseModel {
// =attributes
@attr('string', {
description:
'The ID of the Credential Store of which this Credential is a part.',
})
credential_store_id;
@attr('string', {
description: 'The type of the resource, to help differentiate schemas',
})
type;
@attr('string', {
description: 'Optional name for identification purposes',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
// =attributes (username_password, ssh_private_key, username_password_domain)
@attr('string', {
for: ['username_password', 'ssh_private_key', 'username_password_domain'],
isNestedAttribute: true,
description: 'The username for credential.',
})
username;
// =attributes (username_password, username_password_domain, password)
@attr('string', {
for: ['username_password', 'username_password_domain', 'password'],
isNestedAttribute: true,
isSecret: true,
description: 'The password for credential.',
})
password;
// =attributes (ssh_private_key)
@attr('string', {
for: 'ssh_private_key',
isNestedAttribute: true,
isSecret: true,
description: 'The private key for credential.',
})
private_key;
@attr('string', {
for: 'ssh_private_key',
isNestedAttribute: true,
isSecret: true,
description: 'The private key passphrase for credential.',
})
private_key_passphrase;
@attr('string', {
for: 'json',
isNestedAttribute: true,
isSecret: true,
description: 'The secret associated with credential.',
})
json_object;
// =attributes (username_password_domain)
@attr('string', {
for: 'username_password_domain',
isNestedAttribute: true,
description: 'The domain for credential.',
})
domain;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -55,224 +50,63 @@ export default class GeneratedHostCatalogModel extends BaseModel {
})
plugin;
// AWS, Azure & GCP
// AWS & Azure
@attr('boolean', {
for: {
type: 'plugin',
name: ['aws', 'azure', 'gcp'],
},
isNestedAttribute: true,
description: '',
})
disable_credential_rotation;
// AWS and GCP specific
@attr('string', {
for: {
type: 'plugin',
name: ['aws', 'gcp'],
},
description:
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
})
worker_filter;
// AWS specific
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
},
isNestedAttribute: true,
description: '',
})
region;
// AWS static credentials
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'static-credential',
},
isNestedSecret: true,
description: '',
})
access_key_id;
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'static-credential',
},
isNestedSecret: true,
description: '',
})
secret_access_key;
// AWS dynamic credentials
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role ARN to use.',
})
role_arn;
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role external ID to use.',
})
role_external_id;
@attr('string', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role session to use.',
})
role_session_name;
@attr('object-as-array', {
for: {
type: 'plugin',
name: 'aws',
credentialType: 'dynamic-credential',
},
isNestedAttribute: true,
description: 'The role tags to use.',
})
role_tags;
// Azure specific
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
tenant_id;
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
client_id;
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
isNestedAttribute: true,
description: '',
})
subscription_id;
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
isNestedSecret: true,
description: '',
})
secret_id;
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
isNestedSecret: true,
description: '',
})
secret_value;
// GCP specific
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description: 'The project ID associated with the service account.',
})
project_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description: 'The deployment area within a region.',
})
zone;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description:
'The email address associated with the service account. The email address used to uniquely identify the service account. It is required for authentication and authorization.',
})
client_email;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
isNestedAttribute: true,
description:
'The unique identifier for the service account that will be impersonated.',
})
target_service_account_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
description:
'The ID of the private key used to sign the JWT and authentication.',
isNestedSecret: true,
})
private_key_id;
@attr('string', {
for: {
type: 'plugin',
name: 'gcp',
},
description:
'The private key used to sign the JWT and obtain a OAuth 2.0 access token.',
isNestedSecret: true,
})
private_key;
}
+2 -46
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -63,54 +58,15 @@ export default class GeneratedHostSetModel extends BaseModel {
plugin;
@attr('number', {
for: {
type: 'plugin',
name: ['aws', 'azure', 'gcp'],
},
description:
"The number of seconds between the time boundary syncs the hosts in this set using this host set's plugin. If not provided a system determined default is used",
})
sync_interval_seconds;
/**
* Host IDs are read-only under normal circumstances. But these can
* be persisted via a dedicated call to `addHosts()`.
*/
@attr({
readOnly: true,
emptyArrayIfMissing: true,
})
host_ids;
@attr('string-array', {
for: {
type: 'plugin',
name: ['aws', 'azure', 'gcp'],
},
emptyArrayIfMissing: true,
trimWhitespace: true,
})
preferred_endpoints;
// AWS & GCP specific
@attr('string-array', {
for: {
type: 'plugin',
name: ['aws', 'gcp'],
},
isNestedAttribute: true,
emptyArrayIfMissing: true,
})
filters;
// Azure specific, comes in from API as "filter". This is to avoid collisions with filter
// Azure specific
@attr('string', {
for: {
type: 'plugin',
name: 'azure',
},
description: '',
isNestedAttribute: true,
})
filter_string;
filter;
}
-11
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -54,10 +49,4 @@ export default class GeneratedHostModel extends BaseModel {
readOnly: true,
})
plugin;
@attr({
emptyArrayIfMissing: true,
readOnly: true,
})
host_set_ids;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -49,23 +44,4 @@ export default class GeneratedManagedGroupModel extends BaseModel {
'The ID of the Auth Method that is associated with this ManagedGroup',
})
auth_method_id;
// =attributes (OIDC)
@attr('string', {
for: 'oidc',
isNestedAttribute: true,
description:
'The boolean expression filter to use to determine membership. Comes in from API as "filter".',
})
filter_string;
// =attributes (LDAP)
@attr('string-array', {
for: 'ldap',
isNestedAttribute: true,
description: 'The list of groups that make up the ManagedGroup',
})
group_names;
}
@@ -1,61 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* Storage Policy is a resource that manages the lifecycle of Session Recordings,
* particularly to meet compliance frameworks.
*/
export default class GeneratedPolicyModel extends BaseModel {
// =attributes
@attr('string', {
description: 'The type of the resource, to help differentiate schemas',
})
type;
@attr('string', {
description: 'Optional name for identification purposes.',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes.',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
@attr('object', {
isNestedAttribute: true,
description:
'Days is the number of days for which a session recording will be retained.',
})
retain_for;
@attr('object', {
isNestedAttribute: true,
description:
'Days is the number of days for which a session recording will be automatically deleted.',
})
delete_after;
}
+3 -9
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -22,12 +17,11 @@ export default class GeneratedRoleModel extends BaseModel {
})
description;
@attr('array', {
emptyArrayIfMissing: true,
@attr('string', {
description:
"The scopes to which this role applies. Defaults to the role's scope.",
"The scope to which this role applies. Defaults to the role's scope.",
})
grant_scope_ids;
grant_scope_id;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -49,9 +44,4 @@ export default class GeneratedScopeModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description: 'The ID of the associated storage policy',
})
storage_policy_id;
}
@@ -1,125 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A session recording contains all data related to session recordings
*/
export default class GeneratedSessionRecordingModel extends BaseModel {
// =attributes
@attr('string', {
description: 'The type of the resource, to help differentiate schemas.',
readOnly: true,
})
type;
@attr('date', {
description:
'The time the Session Recording was created in the controller.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time of the most recent update to the Session Recording.',
readOnly: true,
})
updated_time;
@attr({
description:
'Contains the values of related fields at the time this Session Recording was created',
readOnly: true,
})
create_time_values;
@attr('string', {
description: 'The ID of the Session which this Session Recording recorded.',
readOnly: true,
})
session_id;
@attr('string', {
description:
'The ID of the Storage Bucket for the Target of this Session Recording',
readOnly: true,
})
storage_bucket_id;
@attr('string', {
description:
'The total number of bytes uploaded from the client in the Session.',
readOnly: true,
})
bytes_up;
@attr('string', {
description:
'The total number of bytes downloaded to the client in the Session.',
readOnly: true,
})
bytes_down;
@attr('number', {
description:
'The total number of errors that occurred during the use of the Session.',
readOnly: true,
})
errors_number;
@attr('date', {
description: 'The time the Session started.',
readOnly: true,
})
start_time;
@attr('date', {
description: 'The time the Session finished.',
readOnly: true,
})
end_time;
@attr({
description: 'The array of mime types that this recording will support.',
readOnly: true,
})
mime_types;
@attr('duration', {
description: 'The duration of this session recording in ms.',
readOnly: true,
})
duration;
@attr('string', {
description:
'The current state of the session recording. One of "started", "available" and "unknown".',
readOnly: true,
})
state;
@attr('string', {
description:
'Any error seen during the closing of the session recording. Currently only set if state is "unknown".',
readOnly: true,
})
error_details;
@attr('date', {
description: 'The time until a session recording is required to be stored.',
readOnly: true,
})
retain_until;
@attr('date', {
description:
'The time a session recording is scheduled to be automatically deleted.',
readOnly: true,
})
delete_after;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -32,13 +27,6 @@ export default class GeneratedSessionModel extends BaseModel {
})
updated_time;
@attr('date', {
description:
'After this time the connection will be expired, e.g. forcefully terminated.\nOutput only.',
readOnly: true,
})
expiration_time;
@attr('number', {
description: 'Current version number of this resource.',
})
@@ -1,134 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* A storage bucket is a resource that that represents a bucket in an external object store.
*/
export default class GeneratedStorageBucketModel extends BaseModel {
// =attributes
@attr('string', {
description: 'The type of the resource, to help differentiate schemas',
})
type;
@attr({
description: 'Plugin information for this resource.',
readOnly: true,
})
plugin;
@attr('string', {
description: 'Optional name for identification purposes.',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes.',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description: 'The name of the bucket in the external object store.',
})
bucket_name;
@attr('string', {
description:
'The base path where session recordings will be stored in the external object store.',
})
bucket_prefix;
@attr('string', {
description:
'An expression used to filter the workers that have network access to a service that is hosting the external object store.',
})
worker_filter;
@attr('string', {
isNestedAttribute: true,
description: 'The AWS region to use.',
trimWhitespace: true,
})
region;
@attr('string', {
isNestedAttribute: true,
description: 'The MinIO location server.',
})
endpoint_url;
//this field is not used in the UI, but this is kept here to replicate the API
@attr('string', {
readOnly: true,
isNestedAttribute: true,
})
secrets_hmac;
@attr('boolean', {
isNestedAttribute: true,
description: 'If set to `true`, credential rotation is not performed.',
})
disable_credential_rotation;
// =attributes (static credentials)
@attr('string', {
isNestedSecret: true,
description: 'The AWS access key ID to use.',
})
access_key_id;
@attr('string', {
isNestedSecret: true,
description: 'The secret access key to use.',
})
secret_access_key;
// =attributes (dynamic credentials)
@attr('string', {
isNestedAttribute: true,
description: 'The role ARN to use.',
trimWhitespace: true,
})
role_arn;
@attr('string', {
isNestedAttribute: true,
description: 'The role external ID to use.',
})
role_external_id;
@attr('string', {
isNestedAttribute: true,
description: 'The role session to use.',
})
role_session_name;
@attr('object-as-array', {
isNestedAttribute: true,
description: 'The role tags to use.',
})
role_tags;
}
+1 -61
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -35,21 +30,10 @@ export default class GeneratedTargetModel extends BaseModel {
@attr('number', {
description: '',
defaultValue: 1,
})
session_connection_limit;
@attr('string', {
description:
'Optional boolean expressions to filter the egress workers that are allowed to satisfy this request.',
})
egress_worker_filter;
@attr('string', {
description:
'Optional boolean expressions to filter the ingress workers that are allowed to satisfy this request.',
})
ingress_worker_filter;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
@@ -71,48 +55,4 @@ export default class GeneratedTargetModel extends BaseModel {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description:
'An IP address or DNS name for the session to connect to. Cannot be used in conjunction with host sources.',
})
address;
@attr('number', {
isNestedAttribute: true,
description: 'The default port a target should use if present.',
})
default_port;
@attr('number', {
isNestedAttribute: true,
description:
"The default port that will be listened on by the client's local proxy.",
})
default_client_port;
@attr('string', {
description: 'The storage bucket associated with this target.',
isNestedAttribute: true,
})
storage_bucket_id;
@attr('boolean', {
description: 'Whether session recording is enabled on the target.',
isNestedAttribute: true,
})
enable_session_recording;
@attr('array', {
description:
'with_aliases specify the aliases that should be created when the target is created. ',
})
with_aliases;
@attr('array', {
readOnly: true,
description:
'with_aliases specify the aliases that should be created when the target is created. ',
})
aliases;
}
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
@@ -1,98 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import BaseModel from '../../models/base';
import { attr } from '@ember-data/model';
/**
* Worker contains all fields related to a worker resource
*/
export default class GeneratedWorkerModel extends BaseModel {
// =attributes
@attr('string', {
description: 'Optional name for identification purposes.',
})
name;
@attr('string', {
description: 'Optional user-set description for identification purposes.',
})
description;
@attr('date', {
description: 'The time this resource was created\nOutput only.',
readOnly: true,
})
created_time;
@attr('date', {
description: 'The time this resource was last updated\nOutput only.',
readOnly: true,
})
updated_time;
@attr('number', {
description: 'Current version number of this resource.',
})
version;
@attr('string', {
description: 'The address that this worker is reachable at.\nOutput only.',
readOnly: true,
})
address;
@attr('date', {
description:
'The time this worker was last reported its status.\nOutput only.',
readOnly: true,
})
last_status_time;
@attr('string', {
description:
'The version of the Boundary binary the worker is running.\nOutput only.',
readOnly: true,
})
release_version;
@attr('number', {
description:
'The number of connections that this worker is currently handling.\nOutput only.',
readOnly: true,
})
active_connection_count;
@attr('string', {
description:
'The type of the worker, denoted by how it authenticates: pki or kms.\nOutput only.',
readOnly: true,
})
type;
@attr({
description:
'The deduplicated union of the tags reported by the worker ' +
'from its configuration and any tags added through other means.\nOutput only.',
readOnly: true,
emptyObjectIfMissing: true,
})
canonical_tags;
@attr({
description:
"The tags set in the worker's configuration file.\nOutput only.",
readOnly: true,
emptyObjectIfMissing: true,
})
config_tags;
@attr({
description: 'The api tags set for the worker.\nOutput only.',
emptyObjectIfMissing: true,
})
api_tags;
}
@@ -1,190 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { service } from '@ember/service';
import { getOwner, setOwner } from '@ember/application';
import { generateMQLExpression } from '../utils/mql-query';
import { paginateResults } from '../utils/paginate-results';
import { underscore } from '@ember/string';
const { __electronLog } = globalThis;
/**
* Not all types are yet supported by the cache daemon so we'll
* just whitelist the ones we need to start with. Keys are the resource
* types and the values are the fields that are searchable by the client.
* @type {{session: string[], alias: string[], target: string[]}}
*/
const supportedTypes = {
target: ['id', 'name', 'description', 'address', 'scope_id'],
session: ['id', 'type', 'status', 'endpoint', 'scope_id', 'target_id'],
alias: ['id', 'type', 'value', 'destination_id'],
};
/**
* Map the model resource name to the cache daemon resource name
* @type {{session: string, alias: string, target: string}}
*/
export const resourceNames = {
target: 'targets',
session: 'sessions',
alias: 'resolvable-aliases',
};
const fetchControllerData = async (context, next) => {
const { data } = context.request;
const { query: originalQuery } = data;
// remove cache daemon specific query params
// eslint-disable-next-line no-unused-vars
const { query, page, pageSize, ...remainingQuery } = originalQuery;
// If we get an error or cache daemon is unavailable, fall back to calling the API
context.request.data.query = remainingQuery;
const results = await next(context.request);
// As a result of https://rfcs.emberjs.com/id/0846-ember-data-deprecate-proxies/ we cannot use proxy array methods like .toArray
// use slice() instead as indicated in the RFC.
const models = results.content.slice();
const paginatedResult = paginateResults(models, page, pageSize);
paginatedResult.meta = { totalItems: models.length };
return paginatedResult;
};
/**
* Handler to sit in front of the API request layer
* so we can request from the daemon first
*/
export default class CacheDaemonHandler {
@service session;
@service ipc;
constructor(context) {
setOwner(this, getOwner(context));
}
async request(context, next) {
switch (context.request.op) {
case 'query': {
const { store, data } = context.request;
const { type, query, options: { pushToStore = true } = {} } = data;
const isCacheDaemonRunning = await this.ipc.invoke(
'isCacheDaemonRunning',
);
// eslint-disable-next-line no-unused-vars
let { recursive, scope_id, page, pageSize, ...remainingQuery } = query;
let searchQuery = '';
if (
!Object.keys(supportedTypes).includes(type) ||
!isCacheDaemonRunning
) {
return fetchControllerData(context, next);
}
if (remainingQuery.query) {
const { search, filters } = remainingQuery.query;
searchQuery = generateMQLExpression({
search: {
text: search,
fields: supportedTypes[type],
},
filters,
});
}
const sessionData = this.session.data?.authenticated;
const auth_token_id = sessionData?.id;
const token = sessionData?.token;
const resourceName = resourceNames[type];
remainingQuery = {
...remainingQuery,
query: searchQuery,
auth_token_id,
token,
resource: resourceName,
};
let cacheDaemonResults = {};
try {
cacheDaemonResults = await this.ipc.invoke(
'searchCacheDaemon',
remainingQuery,
);
} catch (e) {
// If we got a 403, most likely the cache daemon was restarted and our token is no longer valid
// I'm not sure if we can get a 401 since we always send a token but we'll handle it in the same way
if (e.statusCode === 403 || e.statusCode === 401) {
try {
await this.ipc.invoke('addTokenToDaemons', {
tokenId: auth_token_id,
token,
});
cacheDaemonResults = await this.ipc.invoke(
'searchCacheDaemon',
remainingQuery,
);
} catch (err) {
// If it fails again just fall back to fetching controller data
__electronLog?.error(
'Failed to add token to daemons',
err.message,
);
}
} else {
__electronLog?.error(
`Failed to search cache daemon for ${resourceName}. Error:`,
e.message,
);
return fetchControllerData(context, next);
}
}
const results = cacheDaemonResults[underscore(resourceName)];
const payload = { items: paginateResults(results, page, pageSize) };
const schema = store.modelFor(type);
const serializer = store.serializerFor(type);
const normalizedPayload = serializer.normalizeResponse(
store,
schema,
payload,
null,
'query',
);
// Return the raw data if we don't push to the store.
let records = normalizedPayload.data.map((record) => ({
...record.attributes,
id: record.id,
}));
if (pushToStore) {
records = store.push(normalizedPayload);
}
// Set a meta property on the array to store the total items of the results.
// This isn't conventional but is better than returning an ArrayProxy
// or EmberArray since the ember store query method asserts it has to be an array
// so we can't just return an object.
// Also include whether the results are incomplete or is still refreshing
// as part of the response
records.meta = {
totalItems: results?.length ?? 0,
isLoadIncomplete: cacheDaemonResults.incomplete ?? false,
// Sometimes the refresh status returns an error status if it takes too long but it's still refreshing
isCacheRefreshing:
cacheDaemonResults.refresh_status !== 'not-refreshing',
};
return records;
}
default:
return next(context.request);
}
}
}
-274
View File
@@ -1,274 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { getOwner, setOwner } from '@ember/owner';
import { service } from '@ember/service';
import { modelMapping } from 'api/services/sqlite';
import { get } from '@ember/object';
import { typeOf } from '@ember/utils';
import { hashCode } from '../utils/hash-code';
import { generateSQLExpressions } from '../utils/sqlite-query';
const isISODateString = (str) =>
typeOf(str) === 'string' &&
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z?$/.test(str);
export default class SqliteHandler {
@service sqlite;
batchLimit = 5_000;
constructor(context) {
setOwner(this, getOwner(context));
}
async request(context, next) {
switch (context.request.op) {
case 'query': {
const { store, data } = context.request;
const {
type,
query,
options: {
pushToStore = true,
peekDb = false,
storeToken = true,
} = {},
} = data;
const supportedModels = Object.keys(modelMapping);
// Go through normal flow if we don't yet support the model
// or if we don't have a sqlite db instance
if (!supportedModels.includes(type) || !this.sqlite.worker) {
return next(context.request);
}
const adapter = store.adapterFor(type);
const schema = store.modelFor(type);
const serializer = store.serializerFor(type);
let {
page,
pageSize,
select,
query: queryObj,
...remainingQuery
} = query;
let payload,
listToken,
writeToDbPromise,
totalInsert = 0;
if (!peekDb) {
const tokenKey = `${type}-${hashCode(remainingQuery)}`;
if (storeToken) {
const [tokenObj] = await this.sqlite.fetchResource({
...generateSQLExpressions('token', {
filters: { id: [{ equals: tokenKey }] },
}),
});
listToken = tokenObj?.token;
} else {
// This is a temporary fix of clearing the DB (specifically for auth-methods)
// since we are not storing the token we do not get back a list of removed_ids
// from the API call and we do not want deleted items from showing in list view.
await this.sqlite.deleteResource(type);
}
do {
try {
payload = await adapter.query(store, schema, {
...remainingQuery,
list_token: listToken,
batchLimit: this.batchLimit,
});
totalInsert += payload.items?.length ?? 0;
// await the previous writeToDbPromise before writing to db again
if (writeToDbPromise) {
await writeToDbPromise;
}
} catch (err) {
payload = await this.retryQueryFailure({
err,
type,
adapter,
store,
schema,
remainingQuery,
});
totalInsert += payload.items?.length ?? 0;
}
listToken = payload.list_token;
writeToDbPromise = this.writeToDb(
payload,
storeToken,
tokenKey,
pushToStore,
serializer,
store,
schema,
type,
);
} while (payload.response_type === 'delta');
await writeToDbPromise;
}
if (totalInsert > 0) {
// If there were any inserts, let sqlite handle running analyze on the DB
await this.sqlite.analyzeDatabase();
}
const { sql, parameters } = generateSQLExpressions(type, queryObj, {
page,
pageSize,
select: select ?? [{ field: 'data' }],
});
const rows = await this.sqlite.fetchResource({
sql,
parameters,
});
const { sql: countSql, parameters: countParams } =
generateSQLExpressions(type, queryObj, {
select: [{ field: '*', isCount: true, alias: 'total' }],
});
const count = await this.sqlite.fetchResource({
sql: countSql,
parameters: countParams,
});
const results = rows.map((item) =>
JSON.parse(item.data, (key, value) =>
// Reviver function to convert any ISO strings back to date objects
// as we had to initially convert them to strings when stored in SQLite
isISODateString(value) ? new Date(value) : value,
),
);
// If we are not pushing to the store, use the raw data with id property
const records = pushToStore
? store.push({ data: results })
: results.map((record) => ({
...record.attributes,
id: record.id,
}));
// Set a meta property on the array to store the total items of the results.
// This isn't conventional but is better than returning an ArrayProxy
// or EmberArray since the ember store query method asserts it has to be an array
// so we can't just return an object.
records.meta = { totalItems: count[0].total };
return records;
}
default:
return next(context.request);
}
}
async retryQueryFailure({
err,
type,
adapter,
store,
schema,
remainingQuery,
}) {
// If we get an invalid list token, we'll delete the token and
// clear the DB and try again without a token.
if (
err?.errors?.[0].status === 400 &&
err?.errors?.[0].code === 'invalid list token'
) {
await this.sqlite.deleteResource(type);
// Delete all tokens of the same resource type so we don't keep clearing the DB.
// Even if other tokens may still be valid, we might have cleared data that they
// depended on so we should clear all tokens of the same type when clearing the DB.
const { sql, parameters } = generateSQLExpressions('token', {
filters: { id: [{ contains: type }] },
});
const tokenRows = await this.sqlite.fetchResource({
sql,
parameters,
});
const tokenIds = tokenRows.map((row) => row.id);
if (tokenIds.length > 0) {
await this.sqlite.deleteResource('token', tokenIds);
}
return await adapter.query(store, schema, {
...remainingQuery,
});
} else {
throw err;
}
}
async writeToDb(
payload,
storeToken,
tokenKey,
pushToStore,
serializer,
store,
schema,
type,
) {
if (payload.list_token && storeToken) {
await this.sqlite.insertResource('token', [
[tokenKey, payload.list_token],
]);
}
const normalizedPayload = serializer.normalizeResponse(
store,
schema,
payload,
null,
'query',
);
const { data: payloadData } = normalizedPayload;
// Remove any records from the DB if the API indicates they've been deleted
// Additionally remove deleted records from ember data store for symmetry
if (payload.removed_ids?.length > 0) {
await this.sqlite.deleteResource(type, payload.removed_ids);
if (pushToStore) {
payload.removed_ids.forEach((id) => {
const record = store.peekRecord(type, id);
store.unloadRecord(record);
});
}
}
// Store the new data we just got back from the API refresh
const items = payloadData.map((datum) => {
const params = Object.values(modelMapping[type]).map((key) => {
let value;
if (key === 'id') {
value = datum.id;
} else {
value = get(datum, `attributes.${key}`);
}
if (typeOf(value) === 'date') {
return value.toISOString();
}
return value;
});
return [...params, JSON.stringify(datum)];
});
await this.sqlite.insertResource(type, items);
}
}
@@ -0,0 +1,15 @@
// app/initializers/fragment-serializer.js
import FragmentAuthMethodAttributesAccountClaimMapSerializer from '../serializers/fragment-auth-method-attributes-account-claim-map';
export function initialize(application) {
application.register(
'serializer:fragment-auth-method-attributes-account-claim-map',
FragmentAuthMethodAttributesAccountClaimMapSerializer
);
}
// Fragment serializers must be manually registered
export default {
name: 'fragment-auth-method-attributes-account-claim-map',
initialize: initialize,
};
@@ -0,0 +1,12 @@
// app/initializers/fragment-serializer.js
import FragmentStringSerializer from '../serializers/fragment-string';
export function initialize(application) {
application.register('serializer:fragment-string', FragmentStringSerializer);
}
// Fragment serializers must be manually registered
export default {
name: 'fragment-string',
initialize: initialize,
};
+2 -7
View File
@@ -1,12 +1,7 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Mixin from '@ember/object/mixin';
/**
* Overrides URL-building methods in an adapter for greater flexibility.
* Overrides URL-building methods in an adpater for greater flexibility.
* Specifically enhances calls to `urlPrefix` with `modelName`, `id`, and
* `snapshot`, which aren't normally passed. And adds support for a
* `urlSuffix` method, the return value of which is appended to the end of
@@ -22,7 +17,7 @@ export default Mixin.create({
/**
* This override behaves identically to the default method by default.
* It exists only to document the additional parameters to the method.
* It exists only to document the additional paramters to the method.
* @override
* @param {string} path
* @param {string} parentURL
+12 -31
View File
@@ -1,54 +1,35 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedAccountModel from '../generated/models/account';
import {
TYPE_AUTH_METHOD_PASSWORD,
TYPE_AUTH_METHOD_OIDC,
TYPE_AUTH_METHOD_LDAP,
TYPES_AUTH_METHOD,
} from './auth-method';
import { fragment } from 'ember-data-model-fragments/attributes';
import { computed } from '@ember/object';
import { equal } from '@ember/object/computed';
export default class AccountModel extends GeneratedAccountModel {
// =attributes
/**
* @type {boolean}
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentAccountAttributesModel}
*/
get isPassword() {
return this.type === TYPE_AUTH_METHOD_PASSWORD;
}
@fragment('fragment-account-attributes', { defaultValue: {} }) attributes;
/**
* @type {boolean}
*/
get isOIDC() {
return this.type === TYPE_AUTH_METHOD_OIDC;
}
@equal('type', 'password') isPassword;
/**
* @type {boolean}
*/
get isLDAP() {
return this.type === TYPE_AUTH_METHOD_LDAP;
}
/**
* @type {boolean}
*/
get isUnknown() {
return !TYPES_AUTH_METHOD.includes(this.type);
}
@equal('type', 'oidc') isOIDC;
/**
* Convenience for getting username in account.
* @type {string}
*/
@computed('attributes.{email,full_name,login_name}')
get accountName() {
const { email, full_name, login_name, subject } = this;
return email || full_name || login_name || subject;
const { email, full_name, login_name } = this.attributes;
return email || full_name || login_name;
}
// =methods
@@ -87,7 +68,7 @@ export default class AccountModel extends GeneratedAccountModel {
changePassword(
currentPassword,
newPassword,
options = { adapterOptions: {} },
options = { adapterOptions: {} }
) {
const defaultAdapterOptions = {
method: 'change-password',
-10
View File
@@ -1,10 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedAliasModel from '../generated/models/alias';
export const TYPE_ALIAS_TARGET = 'target';
export const TYPES_ALIAS = Object.freeze([TYPE_ALIAS_TARGET]);
export default class AliasModel extends GeneratedAliasModel {}
+37 -52
View File
@@ -1,26 +1,12 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedAuthMethodModel from '../generated/models/auth-method';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { equal } from '@ember/object/computed';
export const TYPE_AUTH_METHOD_PASSWORD = 'password';
export const TYPE_AUTH_METHOD_OIDC = 'oidc';
export const TYPE_AUTH_METHOD_LDAP = 'ldap';
export const TYPES_AUTH_METHOD = Object.freeze([
TYPE_AUTH_METHOD_PASSWORD,
TYPE_AUTH_METHOD_OIDC,
TYPE_AUTH_METHOD_LDAP,
]);
/**
* Enum options per auth method type and field.
*/
export const options = {
state: ['inactive', 'active-private', 'active-public'],
oidc: {
signing_algorithms: [
'RS256',
@@ -34,27 +20,42 @@ export const options = {
'PS512',
'EdDSA',
],
state: ['inactive', 'active-private', 'active-public'],
account_claim_maps: {
to: ['sub', 'name', 'email'],
},
prompts: ['consent', 'select_account', 'login'],
},
ldap: {
account_attribute_maps: {
to: ['fullName', 'email'],
},
dereference_aliases: {
NeverDerefAliases: 'never',
DerefInSearching: 'searching',
DerefFindingBaseObj: 'finding',
DerefAlways: 'always',
},
},
};
export default class AuthMethodModel extends GeneratedAuthMethodModel {
// =error attributes
// These attributes exist solely to capture errors on nested fields.
// See the application adapter's error normalization method for
// more information.
@attr('string', { readOnly: true }) attributes_state;
@attr('string', { readOnly: true }) attributes_issuer;
@attr('string', { readOnly: true }) attributes_client_id;
@attr('string', { readOnly: true }) attributes_client_secret;
@attr('string', { readOnly: true }) attributes_max_age;
@attr('string', { readOnly: true }) attributes_api_url_prefix;
@attr('string', { readOnly: true })
attributes_disable_discovered_config_validation;
@attr('string', { readOnly: true }) attributes_dry_run;
@attr('string', { readOnly: true }) attributes_account_claim_maps;
@attr('string', { readOnly: true }) attributes_claims_scopes;
@attr('string', { readOnly: true }) attributes_signing_algorithms;
@attr('string', { readOnly: true }) attributes_allowed_audiences;
@attr('string', { readOnly: true }) attributes_idp_ca_certs;
// =attributes
/**
* Attributes of this resource, if any, represented as a JSON fragment.
* @type {FragmentAuthMethodAttributesModel}
*/
@fragment('fragment-auth-method-attributes', { defaultValue: {} }) attributes;
/**
* @type {boolean}
*/
@@ -66,57 +67,43 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel {
* @type {boolean}
*/
get isPassword() {
return this.type === TYPE_AUTH_METHOD_PASSWORD;
return this.type === 'password';
}
/**
* @type {boolean}
*/
get isOIDC() {
return this.type === TYPE_AUTH_METHOD_OIDC;
}
/**
* @type {boolean}
*/
get isLDAP() {
return this.type === TYPE_AUTH_METHOD_LDAP;
}
/**
* @type {boolean}
*/
get isUnknown() {
return !TYPES_AUTH_METHOD.includes(this.type);
return this.type === 'oidc';
}
/**
* @type {boolean}
*/
get isInactive() {
return this.state === 'inactive';
return this.attributes.state === 'inactive';
}
/**
* @type {boolean}
*/
get isPrivate() {
return this.state === 'active-private';
return this.attributes.state === 'active-private';
}
/**
* @type {boolean}
*/
get isPublic() {
return this.state === 'active-public';
return this.attributes.state === 'active-public';
}
// =methods
/**
* Change the active and visibility state of an OIDC and LDAP auth method
* Change the active and visibility state of an OIDC auth method
* given its ID.
* For OIDC and LDAP auth methods only.
* For OIDC auth methods only.
* @param {[string]} state
* @param {object} options
* @param {object} options.adapterOptions
@@ -124,11 +111,9 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel {
*/
changeState(state, options = { adapterOptions: {} }) {
const defaultAdapterOptions = {
method: 'change-state',
state,
};
if (this.isOIDC) {
defaultAdapterOptions.method = 'change-state';
}
return this.save({
...options,
adapterOptions: {
-5
View File
@@ -1,8 +1,3 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedAuthTokenModel from '../generated/models/auth-token';
export default class AuthTokenModel extends GeneratedAuthTokenModel {}
+6 -20
View File
@@ -1,12 +1,7 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Model from '@ember-data/model';
import { attr } from '@ember-data/model';
import { fragment } from 'ember-data-model-fragments/attributes';
import { computed } from '@ember/object';
import { service } from '@ember/service';
/**
* Base model class provides common functionality for all models.
@@ -31,16 +26,13 @@ import { service } from '@ember/service';
* 2) Or as an ID: `record.scopeID`.
*/
export default class BaseModel extends Model {
// =services
@service store;
// =relationships
/**
* Scope of this resource, if any, represented as a JSON fragment.
* @type {FragmentScope}
*/
@attr() scope;
@fragment('fragment-scope') scope;
// =attributes
@@ -57,20 +49,12 @@ export default class BaseModel extends Model {
get scopeModel() {
const id = this.scopeID;
return id ? this.store.peekRecord('scope', id) : null;
return this.store.peekRecord('scope', id);
}
// Ember seems to have a bug with its internals and can't correctly set
// the `scopeModel` when passed in as part of the `inputProperties`
// of a `createRecord` call
set scopeModel(model) {
if (model) {
const json = model.serialize();
json.scope_id = model.id;
json.type = model.type;
json.isGlobal = model.isGlobal;
json.isOrg = model.isOrg;
json.isProject = model.isProject;
this.scope = json;
}
}
@@ -101,6 +85,7 @@ export default class BaseModel extends Model {
/**
* @type {boolean}
*/
@computed('hasDirtyAttributes', 'isSaving')
get canSave() {
return this.hasDirtyAttributes && !this.isSaving;
}
@@ -108,6 +93,7 @@ export default class BaseModel extends Model {
/**
* @type {boolean}
*/
@computed('canSave')
get cannotSave() {
return !this.canSave;
}
@@ -1,34 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedChannelRecordingModel from '../generated/models/channel-recording';
import { belongsTo } from '@ember-data/model';
export const MIME_TYPE_ASCIICAST = 'application/x-asciicast';
export default class ChannelRecordingModel extends GeneratedChannelRecordingModel {
@belongsTo('connection-recording', {
async: false,
inverse: 'channel_recordings',
})
connection_recording;
/**
* True if the channel supports asciicast.
* @type {boolean}
*/
get isAsciicast() {
return this.mime_types?.includes(MIME_TYPE_ASCIICAST) ?? false;
}
/**
* Download the ascii cast recording for this channel.
* @returns {Promise<string>}
*/
getAsciicast() {
const adapter = this.store.adapterFor('session-recording');
return adapter.getAsciicast(this.id);
}
}
@@ -1,20 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import GeneratedConnectionRecordingModel from '../generated/models/connection-recording';
import { belongsTo, hasMany } from '@ember-data/model';
export default class ConnectionRecordingModel extends GeneratedConnectionRecordingModel {
@belongsTo('session-recording', {
async: false,
inverse: 'connection_recordings',
})
session_recording;
@hasMany('channel-recording', {
async: false,
inverse: 'connection_recording',
})
channel_recordings;
}
Loaded 100 of 2982 files, more files were not shown because too many files have changed in this diff. Show more