Compare commits
No commits in common. "main" and "prod-beta" have entirely different histories.
459 changed files with 37130 additions and 72115 deletions
6
.eslintignore
Normal file
6
.eslintignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Ignore programatically generated API slices
|
||||
imageBuilderApi.ts
|
||||
contentSourcesApi.ts
|
||||
rhsmApi.ts
|
||||
provisioningApi.ts
|
||||
edgeApi.ts
|
||||
4
.eslintrc-typescript.yml
Normal file
4
.eslintrc-typescript.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
rules:
|
||||
"@typescript-eslint/no-unused-vars": "error"
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
|
||||
42
.eslintrc.yml
Normal file
42
.eslintrc.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
extends: [
|
||||
"@redhat-cloud-services/eslint-config-redhat-cloud-services",
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
globals:
|
||||
insights: 'readonly'
|
||||
shallow: readonly
|
||||
render: 'readonly'
|
||||
mount: 'readonly'
|
||||
plugins:
|
||||
- import
|
||||
rules:
|
||||
rulesdir/forbid-pf-relative-imports: off
|
||||
import/order:
|
||||
- error
|
||||
- groups:
|
||||
- builtin
|
||||
- external
|
||||
- internal
|
||||
- sibling
|
||||
- parent
|
||||
- index
|
||||
alphabetize:
|
||||
order: asc
|
||||
caseInsensitive: true
|
||||
newlines-between: always
|
||||
pathGroups: # ensures the import of React is always on top
|
||||
- pattern: react
|
||||
group: builtin
|
||||
position: before
|
||||
pathGroupsExcludedImportTypes:
|
||||
- react
|
||||
prefer-const:
|
||||
- error
|
||||
- destructuring: any
|
||||
no-console: 2
|
||||
eqeqeq: error
|
||||
overrides:
|
||||
- files: "**/*.ts?(x)"
|
||||
parser: "@typescript-eslint/parser"
|
||||
extends: ".eslintrc-typescript.yml"
|
||||
|
|
@ -1 +0,0 @@
|
|||
1
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
---
|
||||
name: Debian Image Builder Frontend CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run build || echo "Build script not found"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ -f package.json ] && npm run test; then
|
||||
npm test
|
||||
else
|
||||
echo "No test script found, skipping tests"
|
||||
fi
|
||||
|
||||
- name: Run linting
|
||||
run: |
|
||||
if [ -f package.json ] && npm run lint; then
|
||||
npm run lint
|
||||
else
|
||||
echo "No lint script found, skipping linting"
|
||||
fi
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-build
|
||||
path: |
|
||||
dist/
|
||||
build/
|
||||
retention-days: 30
|
||||
|
||||
package:
|
||||
name: Package Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
devscripts \
|
||||
debhelper \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Create debian directory
|
||||
run: |
|
||||
mkdir -p debian
|
||||
cat > debian/control << EOF
|
||||
Source: debian-image-builder-frontend
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Debian Forge Team <team@debian-forge.org>
|
||||
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
|
||||
Standards-Version: 4.6.2
|
||||
|
||||
Package: debian-image-builder-frontend
|
||||
Architecture: all
|
||||
Depends: \${misc:Depends}, nodejs, nginx
|
||||
Description: Debian Image Builder Frontend
|
||||
Web-based frontend for Debian Image Builder with Cockpit integration.
|
||||
Provides a user interface for managing image builds, blueprints,
|
||||
and system configurations through a modern React application.
|
||||
EOF
|
||||
|
||||
cat > debian/rules << EOF
|
||||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh \$@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install
|
||||
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
|
||||
|
||||
# Copy built frontend files
|
||||
if [ -d dist ]; then
|
||||
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
elif [ -d build ]; then
|
||||
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
fi
|
||||
|
||||
# Copy source files for development
|
||||
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
|
||||
# Create nginx configuration
|
||||
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/debian-image-builder-frontend;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
|
||||
# Create cockpit manifest
|
||||
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
|
||||
{
|
||||
"version": 1,
|
||||
"manifest": {
|
||||
"name": "debian-image-builder",
|
||||
"version": "1.0.0",
|
||||
"title": "Debian Image Builder",
|
||||
"description": "Build and manage Debian atomic images",
|
||||
"url": "/usr/share/debian-image-builder-frontend",
|
||||
"icon": "debian-logo",
|
||||
"requires": {
|
||||
"cockpit": ">= 200"
|
||||
}
|
||||
}
|
||||
}
|
||||
COCKPIT_EOF
|
||||
EOF
|
||||
|
||||
cat > debian/changelog << EOF
|
||||
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* Debian Image Builder Frontend with Cockpit integration
|
||||
* React-based web interface for image management
|
||||
|
||||
-- Debian Forge Team <team@debian-forge.org> $(date -R)
|
||||
EOF
|
||||
|
||||
cat > debian/compat << EOF
|
||||
13
|
||||
EOF
|
||||
|
||||
chmod +x debian/rules
|
||||
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
dpkg-buildpackage -us -uc -b
|
||||
ls -la ../*.deb
|
||||
|
||||
- name: Upload Debian package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debian-image-builder-frontend-deb
|
||||
path: ../*.deb
|
||||
retention-days: 30
|
||||
|
||||
cockpit-integration:
|
||||
name: Test Cockpit Integration
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test cockpit integration
|
||||
run: |
|
||||
echo "Testing Cockpit integration..."
|
||||
if [ -d cockpit ]; then
|
||||
echo "Cockpit directory found:"
|
||||
ls -la cockpit/
|
||||
else
|
||||
echo "No cockpit directory found"
|
||||
fi
|
||||
|
||||
if [ -f package.json ]; then
|
||||
echo "Package.json scripts:"
|
||||
npm run
|
||||
fi
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
---
|
||||
name: Debian Image Builder Frontend CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
NODE_VERSION: "18"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: Build and Test Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm run build || echo "Build script not found"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ -f package.json ] && npm run test; then
|
||||
npm test
|
||||
else
|
||||
echo "No test script found, skipping tests"
|
||||
fi
|
||||
|
||||
- name: Run linting
|
||||
run: |
|
||||
if [ -f package.json ] && npm run lint; then
|
||||
npm run lint
|
||||
else
|
||||
echo "No lint script found, skipping linting"
|
||||
fi
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-build
|
||||
path: |
|
||||
dist/
|
||||
build/
|
||||
retention-days: 30
|
||||
|
||||
package:
|
||||
name: Package Frontend
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
devscripts \
|
||||
debhelper \
|
||||
git \
|
||||
ca-certificates \
|
||||
python3
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build production bundle
|
||||
run: |
|
||||
if [ -f package.json ] && npm run build; then
|
||||
npm run build
|
||||
else
|
||||
echo "No build script found"
|
||||
fi
|
||||
|
||||
- name: Create debian directory
|
||||
run: |
|
||||
mkdir -p debian
|
||||
cat > debian/control << EOF
|
||||
Source: debian-image-builder-frontend
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Debian Forge Team <team@debian-forge.org>
|
||||
Build-Depends: debhelper (>= 13), nodejs, npm, git, ca-certificates
|
||||
Standards-Version: 4.6.2
|
||||
|
||||
Package: debian-image-builder-frontend
|
||||
Architecture: all
|
||||
Depends: \${misc:Depends}, nodejs, nginx
|
||||
Description: Debian Image Builder Frontend
|
||||
Web-based frontend for Debian Image Builder with Cockpit integration.
|
||||
Provides a user interface for managing image builds, blueprints,
|
||||
and system configurations through a modern React application.
|
||||
EOF
|
||||
|
||||
cat > debian/rules << EOF
|
||||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh \$@
|
||||
|
||||
override_dh_auto_install:
|
||||
dh_auto_install
|
||||
mkdir -p debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/nginx/sites-available
|
||||
mkdir -p debian/debian-image-builder-frontend/etc/cockpit
|
||||
|
||||
# Copy built frontend files
|
||||
if [ -d dist ]; then
|
||||
cp -r dist/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
elif [ -d build ]; then
|
||||
cp -r build/* debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
fi
|
||||
|
||||
# Copy source files for development
|
||||
cp -r src debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
cp package.json debian/debian-image-builder-frontend/usr/share/debian-image-builder-frontend/
|
||||
|
||||
# Create nginx configuration
|
||||
cat > debian/debian-image-builder-frontend/etc/nginx/sites-available/debian-image-builder-frontend << 'NGINX_EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/debian-image-builder-frontend;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:8080/;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
|
||||
# Create cockpit manifest
|
||||
cat > debian/debian-image-builder-frontend/etc/cockpit/debian-image-builder.manifest << 'COCKPIT_EOF'
|
||||
{
|
||||
"version": 1,
|
||||
"manifest": {
|
||||
"name": "debian-image-builder",
|
||||
"version": "1.0.0",
|
||||
"title": "Debian Image Builder",
|
||||
"description": "Build and manage Debian atomic images",
|
||||
"url": "/usr/share/debian-image-builder-frontend",
|
||||
"icon": "debian-logo",
|
||||
"requires": {
|
||||
"cockpit": ">= 200"
|
||||
}
|
||||
}
|
||||
}
|
||||
COCKPIT_EOF
|
||||
EOF
|
||||
|
||||
cat > debian/changelog << EOF
|
||||
debian-image-builder-frontend (1.0.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release
|
||||
* Debian Image Builder Frontend with Cockpit integration
|
||||
* React-based web interface for image management
|
||||
|
||||
-- Debian Forge Team <team@debian-forge.org> $(date -R)
|
||||
EOF
|
||||
|
||||
cat > debian/compat << EOF
|
||||
13
|
||||
EOF
|
||||
|
||||
chmod +x debian/rules
|
||||
|
||||
- name: Build Debian package
|
||||
run: |
|
||||
dpkg-buildpackage -us -uc -b
|
||||
ls -la ../*.deb
|
||||
|
||||
- name: Upload Debian package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debian-image-builder-frontend-deb
|
||||
path: ../*.deb
|
||||
retention-days: 30
|
||||
|
||||
cockpit-integration:
|
||||
name: Test Cockpit Integration
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:18-bullseye
|
||||
needs: build-and-test
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
run: |
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test cockpit integration
|
||||
run: |
|
||||
echo "Testing Cockpit integration..."
|
||||
if [ -d cockpit ]; then
|
||||
echo "Cockpit directory found:"
|
||||
ls -la cockpit/
|
||||
else
|
||||
echo "No cockpit directory found"
|
||||
fi
|
||||
|
||||
if [ -f package.json ]; then
|
||||
echo "Package.json scripts:"
|
||||
npm run
|
||||
fi
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
|
|
@ -7,9 +7,3 @@ updates:
|
|||
time: "04:00"
|
||||
open-pull-requests-limit: 3
|
||||
rebase-strategy: "auto"
|
||||
ignore:
|
||||
- dependency-name: "@playwright/test"
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
- "version-update:semver-minor"
|
||||
- "version-update:semver-patch"
|
||||
|
|
|
|||
24
.github/workflows/create-tag.yml
vendored
24
.github/workflows/create-tag.yml
vendored
|
|
@ -1,24 +0,0 @@
|
|||
# This action creates a release every second Wednesday
|
||||
name: "Create and push release tag"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 8 * * 3"
|
||||
|
||||
jobs:
|
||||
tag-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Even or odd week
|
||||
run: if [ `expr \`date +\%s\` / 86400 \% 2` -eq 0 ]; then echo "WEEK=odd" >> $GITHUB_ENV; else echo "WEEK=even" >> $GITHUB_ENV; fi
|
||||
shell: bash
|
||||
|
||||
- name: Upstream tag
|
||||
uses: osbuild/release-action@create-tag
|
||||
if: ${{ env.WEEK == 'odd' || github.event_name != 'schedule' }}
|
||||
with:
|
||||
token: "${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}"
|
||||
username: "imagebuilder-bot"
|
||||
email: "imagebuilder-bots+imagebuilder-bot@redhat.com"
|
||||
83
.github/workflows/dev-checks.yml
vendored
83
.github/workflows/dev-checks.yml
vendored
|
|
@ -1,83 +0,0 @@
|
|||
name: Development checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run build
|
||||
run: npm run build
|
||||
|
||||
lint-checks:
|
||||
name: ESLint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run lint check
|
||||
run: npm run lint
|
||||
|
||||
circular-dependencies:
|
||||
name: Circular Dependencies Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Check for circular dependencies
|
||||
run: npm run circular
|
||||
|
||||
api-changes:
|
||||
name: Manual API Changes Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Check for manual changes to API
|
||||
run: |
|
||||
npm run api
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo
|
||||
echo "✗ API manually changed, please refer to the README for the procedure to follow for programmatically generated API endpoints."
|
||||
exit 1
|
||||
else
|
||||
echo
|
||||
echo "✓ No manual API changes."
|
||||
exit 0
|
||||
fi
|
||||
90
.github/workflows/playwright.yml
vendored
90
.github/workflows/playwright.yml
vendored
|
|
@ -1,90 +0,0 @@
|
|||
name: Hosted playwright tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, labeled, unlabeled]
|
||||
workflow_dispatch:
|
||||
merge_group:
|
||||
|
||||
# this prevents multiple jobs from the same pr
|
||||
# running when new changes are pushed.
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
playwright-tests:
|
||||
runs-on:
|
||||
- codebuild-image-builder-frontend-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- instance-size:large
|
||||
- buildspec-override:true
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Get current PR URL
|
||||
id: get-pr-url
|
||||
run: |
|
||||
# Extract the pull request URL from the event payload
|
||||
pr_url=$(jq -r '.pull_request.html_url' < "$GITHUB_EVENT_PATH")
|
||||
echo "Pull Request URL: $pr_url"
|
||||
# Set the PR URL as an output using the environment file
|
||||
echo "pr_url=$pr_url" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
|
||||
- name: Install front-end dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install playwright
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
# This prevents an error related to minimum watchers when running the front-end and playwright
|
||||
- name: Increase file watchers limit
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
|
||||
- name: Update /etc/hosts
|
||||
run: sudo npm run patch:hosts
|
||||
|
||||
- name: Start front-end server
|
||||
run: |
|
||||
npm run start:federated &
|
||||
npx wait-on http://localhost:8003/apps/image-builder/
|
||||
|
||||
- name: Run testing proxy
|
||||
run: docker run -d --network=host -e HTTPS_PROXY=$RH_PROXY_URL -v "$(pwd)/config:/config:ro,Z" --name consoledot-testing-proxy quay.io/dvagner/consoledot-testing-proxy
|
||||
|
||||
- name: Run front-end Playwright tests
|
||||
env:
|
||||
BASE_URL: https://stage.foo.redhat.com:1337
|
||||
run: |
|
||||
export PLAYWRIGHT_USER=image-builder-playwright-$RANDOM
|
||||
export PLAYWRIGHT_PASSWORD=image-builder-playwright-$(uuidgen)
|
||||
# Step 1: Create a new empty account
|
||||
curl -k -X POST https://account-manager-stage.app.eng.rdu2.redhat.com/account/new -d "{\"username\": \"$PLAYWRIGHT_USER\", \"password\":\"$PLAYWRIGHT_PASSWORD\"}"
|
||||
# Step 2: Attach subscriptions to the new account
|
||||
curl -k -X POST https://account-manager-stage.app.eng.rdu2.redhat.com/account/attach \
|
||||
-d "{\"username\": \"$PLAYWRIGHT_USER\", \"password\":\"$PLAYWRIGHT_PASSWORD\", \"sku\":[\"RH00003\"],\"quantity\": 1}"
|
||||
# Step 3: Activate the new account by accepting Terms and Conditions
|
||||
curl -k -X POST https://account-manager-stage.app.eng.rdu2.redhat.com/account/activate -d "{\"username\": \"$PLAYWRIGHT_USER\", \"password\":\"$PLAYWRIGHT_PASSWORD\"}"
|
||||
# Step 4: Refresh account to update subscription pools
|
||||
curl -k -X POST https://account-manager-stage.app.eng.rdu2.redhat.com/account/refresh -d "{\"username\": \"$PLAYWRIGHT_USER\", \"password\":\"$PLAYWRIGHT_PASSWORD\"}"
|
||||
# Step 5: View account to check account status
|
||||
curl -k -X GET "https://account-manager-stage.app.eng.rdu2.redhat.com/account/get?username=$PLAYWRIGHT_USER&password=$PLAYWRIGHT_PASSWORD"
|
||||
|
||||
CURRENTS_PROJECT_ID=hIU6nO CURRENTS_RECORD_KEY=$CURRENTS_RECORD_KEY npx playwright test
|
||||
|
||||
- name: Store front-end Test report
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 10
|
||||
19
.github/workflows/pr_best_practices.yml
vendored
19
.github/workflows/pr_best_practices.yml
vendored
|
|
@ -1,19 +0,0 @@
|
|||
name: "Verify PR best practices"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [main]
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
pr-best-practices:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: PR best practice check
|
||||
uses: osbuild/pr-best-practices@main
|
||||
with:
|
||||
token: ${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}
|
||||
jira_token: ${{ secrets.IMAGEBUILDER_BOT_JIRA_TOKEN }}
|
||||
47
.github/workflows/release.yml
vendored
47
.github/workflows/release.yml
vendored
|
|
@ -1,47 +0,0 @@
|
|||
name: "Create GitHub release"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# create release artefact before creating the release to get the correct release in the
|
||||
# artefact name.
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Make dist
|
||||
run: |
|
||||
make dist
|
||||
RELEASE_NO=$(echo ${{github.ref_name}} | tr -d 'v')
|
||||
mv "cockpit-image-builder-$RELEASE_NO.tar.gz" ../cockpit-image-builder-$RELEASE_NO.tar.gz
|
||||
|
||||
# create release, which will bump the version
|
||||
- name: Upstream release
|
||||
uses: osbuild/release-action@main
|
||||
with:
|
||||
token: "${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}"
|
||||
slack_webhook_url: "${{ secrets.SLACK_WEBHOOK_URL }}"
|
||||
|
||||
# upload release artefact
|
||||
# Source0 expands to `https://github.com/osbuild/image-builder-frontend/releases/download/v$VERSION/cockpit-image-builder-v$VERSION.tar.gz`,
|
||||
# so the v needs to be in the tarball when we upload it as a release artefact.
|
||||
- name: Upload release artefact
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
RELEASE_NO=$(echo ${{github.ref_name}} | tr -d 'v')
|
||||
gh release upload ${{github.ref_name}} \
|
||||
../cockpit-image-builder-$RELEASE_NO.tar.gz
|
||||
33
.github/workflows/sentry.yml
vendored
33
.github/workflows/sentry.yml
vendored
|
|
@ -1,33 +0,0 @@
|
|||
name: sentryInit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_hash:
|
||||
description: 'The commit hash (or branch/tag) to build'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
createSentryRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_hash || 'refs/heads/master' }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
ENABLE_SENTRY: ${{ secrets.ENABLE_SENTRY }}
|
||||
SENTRY_RELEASE: ${{ github.event.inputs.commit_hash && github.event.inputs.commit_hash }}
|
||||
SENTRY_AUTH_TOKEN: ${{ github.event.inputs.commit_hash && secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
run: npm run build --if-present
|
||||
1
.github/workflows/stale-cleanup.yml
vendored
1
.github/workflows/stale-cleanup.yml
vendored
|
|
@ -8,7 +8,6 @@ jobs:
|
|||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write # needed to clean up the saved action state
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
|
|
|
|||
79
.github/workflows/sync-branches.yml
vendored
Normal file
79
.github/workflows/sync-branches.yml
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
name: Sync branches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source:
|
||||
description: Source ref (branch or sha)
|
||||
required: true
|
||||
type: string
|
||||
target:
|
||||
description: Target branch
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- stage-stable
|
||||
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Validate source and target refs
|
||||
timeout-minutes: 1
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.source }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check ancestry
|
||||
if: ${{ github.event.inputs.target == 'production' }}
|
||||
run: |
|
||||
if ! git merge-base --is-ancestor ${{ github.event.inputs.source }} origin/main; then
|
||||
echo "Target is production and source ref isn't an ancestor of main"
|
||||
exit 1
|
||||
fi
|
||||
if ! git merge-base --is-ancestor ${{ github.event.inputs.source }} origin/stage-stable; then
|
||||
echo "Target is production and source ref isn't deployed in stage-stable"
|
||||
echo "The main and stage-stable branches should be in sync, please fix"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check stage-stable manual sync
|
||||
if: ${{ github.event.inputs.target == 'stage-stable' }}
|
||||
run: |
|
||||
if [ $(git rev-parse ${{ github.event.inputs.source }}) != $(git rev-parse origin/main) ]; then
|
||||
echo "Target is stage-stable and source ref isn't main"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
sync:
|
||||
name: Sync source and target refs
|
||||
needs: check
|
||||
if: ${{ github.event_name == 'push' || success() }}
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.source }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Release to production
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'production' }}
|
||||
run: |
|
||||
git push https://${{ secrets.SCHUTZBOT_GH_TOKEN }}@github.com/${{ github.repository }}.git ${{ github.event.inputs.source }}:refs/heads/prod-beta
|
||||
git push https://${{ secrets.SCHUTZBOT_GH_TOKEN }}@github.com/${{ github.repository }}.git ${{ github.event.inputs.source }}:refs/heads/prod-stable
|
||||
|
||||
- name: Sync main to stage-stable
|
||||
if: ${{ github.event_name == 'push' || github.event.inputs.target == 'stage-stable' }}
|
||||
run: |
|
||||
git push https://${{ secrets.SCHUTZBOT_GH_TOKEN }}@github.com/${{ github.repository }}.git origin/main:refs/heads/stage-stable
|
||||
44
.github/workflows/trigger-gitlab.yml
vendored
44
.github/workflows/trigger-gitlab.yml
vendored
|
|
@ -3,58 +3,25 @@
|
|||
name: Trigger GitLab CI
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Development checks"]
|
||||
types: [completed]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
trigger-gitlab:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IMAGEBUILDER_BOT_GITLAB_SSH_KEY: ${{ secrets.IMAGEBUILDER_BOT_GITLAB_SSH_KEY }}
|
||||
GITLAB_TOKEN: ${{ secrets.IMAGEBUILDER_BOT_GITLAB_PIPELINE_TRIGGER_TOKEN }}
|
||||
steps:
|
||||
- name: Report status
|
||||
uses: haya14busa/action-workflow_run-status@v1
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt install -y jq
|
||||
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: octokit/request-action@v2.x
|
||||
id: fetch_pulls
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
route: GET /repos/${{ github.repository }}/pulls
|
||||
per_page: 100
|
||||
|
||||
- name: Checkout branch
|
||||
id: pr_data
|
||||
env:
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
run: |
|
||||
PR_DATA=$(mktemp)
|
||||
# use uuid as a file terminator to avoid conflicts with data content
|
||||
cat > "$PR_DATA" <<'a21b3e7f-d5eb-44a3-8be0-c2412851d2e6'
|
||||
${{ steps.fetch_pulls.outputs.data }}
|
||||
a21b3e7f-d5eb-44a3-8be0-c2412851d2e6
|
||||
|
||||
PR=$(jq -rc '.[] | select(.head.sha | contains("${{ github.event.workflow_run.head_sha }}")) | select(.state | contains("open"))' "$PR_DATA" | jq -r .number)
|
||||
if [ ! -z "$PR" ]; then
|
||||
echo "pr_branch=PR-$PR" >> "$GITHUB_OUTPUT"
|
||||
git checkout -b PR-$PR
|
||||
else
|
||||
git checkout "${BRANCH}"
|
||||
fi
|
||||
|
||||
- name: Push to gitlab
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
|
|
@ -63,5 +30,4 @@ jobs:
|
|||
touch ~/.ssh/known_hosts
|
||||
ssh-keyscan -t rsa gitlab.com >> ~/.ssh/known_hosts
|
||||
git remote add ci git@gitlab.com:redhat/services/products/image-builder/ci/image-builder-frontend.git
|
||||
[[ "${SKIP_CI}" == true ]] && PUSH_OPTION='-o ci.variable="SKIP_CI=true"' || PUSH_OPTION=""
|
||||
git push -f ${PUSH_OPTION} ci
|
||||
git push -f ci
|
||||
|
|
|
|||
51
.github/workflows/unit-tests.yml
vendored
51
.github/workflows/unit-tests.yml
vendored
|
|
@ -1,51 +0,0 @@
|
|||
name: Unit Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
merge_group:
|
||||
|
||||
# this prevents multiple jobs from the same pr
|
||||
# running when new changes are pushed.
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Service Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run unit tests
|
||||
run: npm run test:coverage
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage/junit.xml
|
||||
verbose: true
|
||||
|
||||
cockpit-unit-tests:
|
||||
name: Cockpit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run unit tests with cockpit
|
||||
run: npm run test:cockpit
|
||||
51
.github/workflows/update-apis.yml
vendored
51
.github/workflows/update-apis.yml
vendored
|
|
@ -1,51 +0,0 @@
|
|||
# This action checks API updates every day at 5:00 UTC.
|
||||
name: Update API code generation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
|
||||
jobs:
|
||||
update-api:
|
||||
name: "Update API definitions"
|
||||
if: github.repository == 'osbuild/image-builder-frontend'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Mark the working directory as safe for git
|
||||
run: git config --global --add safe.directory "$(pwd)"
|
||||
|
||||
- name: Run API code generation
|
||||
run: npm run api
|
||||
|
||||
- name: Check if there are any changes
|
||||
run: |
|
||||
if [ "$(git status --porcelain)" ]; then
|
||||
echo
|
||||
echo "API codegen is up-to-date"
|
||||
exit "0"
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: update-apis
|
||||
delete-branch: true
|
||||
title: "api: regenerate api code generation"
|
||||
commit-message: "api: regenerate api code generation"
|
||||
body: Update api code generation
|
||||
token: ${{ secrets.SCHUTZBOT_GITHUB_ACCESS_TOKEN }}
|
||||
author: schutzbot <schutzbot@gmail.com>
|
||||
28
.gitignore
vendored
28
.gitignore
vendored
|
|
@ -12,9 +12,6 @@ node_modules
|
|||
# production
|
||||
dist
|
||||
|
||||
# cache
|
||||
.cache
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
|
@ -23,29 +20,4 @@ yarn-error.log*
|
|||
coverage
|
||||
|
||||
*~
|
||||
*.swp
|
||||
bots
|
||||
|
||||
# madge graph of dependencies generated by `npm run circular:graph`
|
||||
deps.png
|
||||
|
||||
# build directories
|
||||
cockpit/public/vendor*
|
||||
cockpit/public/src_*
|
||||
cockpit/public/main*
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
||||
# cockpit lib dir
|
||||
pkg/lib
|
||||
|
||||
rpmbuild
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
.env
|
||||
.auth
|
||||
|
|
|
|||
|
|
@ -3,41 +3,31 @@ stages:
|
|||
- test
|
||||
- finish
|
||||
|
||||
.terraform:
|
||||
after_script:
|
||||
- schutzbot/update_github_status.sh update
|
||||
tags:
|
||||
- terraform
|
||||
|
||||
init:
|
||||
stage: init
|
||||
interruptible: true
|
||||
tags:
|
||||
- shell
|
||||
script:
|
||||
- schutzbot/update_github_status.sh start
|
||||
|
||||
test:
|
||||
before_script:
|
||||
- mkdir -p /tmp/artifacts
|
||||
- schutzbot/ci_details.sh > /tmp/artifacts/ci-details-before-run.txt
|
||||
- cat schutzbot/team_ssh_keys.txt | tee -a ~/.ssh/authorized_keys > /dev/null
|
||||
SonarQube:
|
||||
stage: test
|
||||
extends: .terraform
|
||||
script:
|
||||
- schutzbot/make_rpm_and_install.sh
|
||||
- schutzbot/playwright_tests.sh
|
||||
after_script:
|
||||
- schutzbot/ci_details.sh > /tmp/artifacts/ci-details-after-run.txt || true
|
||||
- schutzbot/unregister.sh || true
|
||||
- schutzbot/update_github_status.sh update || true
|
||||
- schutzbot/save_journal.sh || true
|
||||
- schutzbot/upload_artifacts.sh
|
||||
tags:
|
||||
- terraform
|
||||
parallel:
|
||||
matrix:
|
||||
- RUNNER:
|
||||
- aws/fedora-41-x86_64
|
||||
- aws/fedora-42-x86_64
|
||||
- aws/rhel-10.1-nightly-x86_64
|
||||
INTERNAL_NETWORK: ["true"]
|
||||
- schutzbot/sonarqube.sh
|
||||
variables:
|
||||
RUNNER: aws/centos-stream-8-x86_64
|
||||
INTERNAL_NETWORK: "true"
|
||||
GIT_DEPTH: 0
|
||||
|
||||
finish:
|
||||
stage: finish
|
||||
dependencies: []
|
||||
tags:
|
||||
- shell
|
||||
script:
|
||||
|
|
|
|||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "build-tools"]
|
||||
path = build-tools
|
||||
url = https://github.com/RedHatInsights/insights-frontend-builder-common.git
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
name = "basic-example"
|
||||
description = "A basic blueprint"
|
||||
version = "0.0.1"
|
||||
7
.madgerc
7
.madgerc
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
4
.stylelintrc.json
Normal file
4
.stylelintrc.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "stylelint-config-recommended-scss",
|
||||
"customSyntax": "postcss-scss"
|
||||
}
|
||||
|
|
@ -1,558 +0,0 @@
|
|||
apiVersion: tekton.dev/v1
|
||||
kind: PipelineRun
|
||||
metadata:
|
||||
annotations:
|
||||
build.appstudio.openshift.io/repo: https://github.com/osbuild/image-builder-frontend?rev={{revision}}
|
||||
build.appstudio.redhat.com/commit_sha: '{{revision}}'
|
||||
build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}'
|
||||
build.appstudio.redhat.com/target_branch: '{{target_branch}}'
|
||||
pipelinesascode.tekton.dev/max-keep-runs: "3"
|
||||
pipelinesascode.tekton.dev/on-cel-expression: (event == "pull_request" && target_branch == "main") || (event == "push" && target_branch.startsWith("gh-readonly-queue/main/"))
|
||||
creationTimestamp:
|
||||
labels:
|
||||
appstudio.openshift.io/application: insights-image-builder
|
||||
appstudio.openshift.io/component: image-builder-frontend
|
||||
pipelines.appstudio.openshift.io/type: build
|
||||
name: image-builder-frontend-on-pull-request
|
||||
namespace: insights-management-tenant
|
||||
spec:
|
||||
params:
|
||||
- name: git-url
|
||||
value: '{{source_url}}'
|
||||
- name: revision
|
||||
value: '{{revision}}'
|
||||
- name: output-image
|
||||
value: quay.io/redhat-user-workloads/insights-management-tenant/insights-image-builder/image-builder-frontend:on-pr-{{revision}}
|
||||
- name: image-expires-after
|
||||
value: 5d
|
||||
- name: dockerfile
|
||||
value: build-tools/Dockerfile
|
||||
- name: path-context
|
||||
value: .
|
||||
pipelineSpec:
|
||||
description: |
|
||||
This pipeline is ideal for building container images from a Containerfile while reducing network traffic.
|
||||
|
||||
_Uses `buildah` to create a container image. It also optionally creates a source image and runs some build-time tests. EC will flag a violation for [`trusted_task.trusted`](https://enterprisecontract.dev/docs/ec-policies/release_policy.html#trusted_task__trusted) if any tasks are added to the pipeline.
|
||||
This pipeline is pushed as a Tekton bundle to [quay.io](https://quay.io/repository/konflux-ci/tekton-catalog/pipeline-docker-build?tab=tags)_
|
||||
finally:
|
||||
- name: show-sbom
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: show-sbom
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: show-summary
|
||||
params:
|
||||
- name: pipelinerun-name
|
||||
value: $(context.pipelineRun.name)
|
||||
- name: git-url
|
||||
value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit)
|
||||
- name: image-url
|
||||
value: $(params.output-image)
|
||||
- name: build-task-status
|
||||
value: $(tasks.build-image-index.status)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: summary
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:3f6e8513cbd70f0416eb6c6f2766973a754778526125ff33d8e3633def917091
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
params:
|
||||
- description: Source Repository URL
|
||||
name: git-url
|
||||
type: string
|
||||
- default: ""
|
||||
description: Revision of the Source Repository
|
||||
name: revision
|
||||
type: string
|
||||
- description: Fully Qualified Output Image
|
||||
name: output-image
|
||||
type: string
|
||||
- default: .
|
||||
description: Path to the source code of an application's component from where to build image.
|
||||
name: path-context
|
||||
type: string
|
||||
- default: Dockerfile
|
||||
description: Path to the Dockerfile inside the context specified by parameter path-context
|
||||
name: dockerfile
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Force rebuild image
|
||||
name: rebuild
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Skip checks against built image
|
||||
name: skip-checks
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Execute the build with network isolation
|
||||
name: hermetic
|
||||
type: string
|
||||
- default: ""
|
||||
description: Build dependencies to be prefetched by Cachi2
|
||||
name: prefetch-input
|
||||
type: string
|
||||
- default: ""
|
||||
description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively.
|
||||
name: image-expires-after
|
||||
- default: "false"
|
||||
description: Build a source image.
|
||||
name: build-source-image
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Add built image into an OCI image index
|
||||
name: build-image-index
|
||||
type: string
|
||||
- default: []
|
||||
description: Array of --build-arg values ("arg=value" strings) for buildah
|
||||
name: build-args
|
||||
type: array
|
||||
- default: ""
|
||||
description: Path to a file with build arguments for buildah, see https://www.mankier.com/1/buildah-build#--build-arg-file
|
||||
name: build-args-file
|
||||
type: string
|
||||
results:
|
||||
- description: ""
|
||||
name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- description: ""
|
||||
name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- description: ""
|
||||
name: CHAINS-GIT_URL
|
||||
value: $(tasks.clone-repository.results.url)
|
||||
- description: ""
|
||||
name: CHAINS-GIT_COMMIT
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
tasks:
|
||||
- name: init
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(params.output-image)
|
||||
- name: rebuild
|
||||
value: $(params.rebuild)
|
||||
- name: skip-checks
|
||||
value: $(params.skip-checks)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: init
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: clone-repository
|
||||
params:
|
||||
- name: url
|
||||
value: $(params.git-url)
|
||||
- name: revision
|
||||
value: $(params.revision)
|
||||
runAfter:
|
||||
- init
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: git-clone
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:7939000e2f92fc8b5d2c4ee4ba9000433c5aa7700d2915a1d4763853d5fd1fd4
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: output
|
||||
workspace: workspace
|
||||
- name: basic-auth
|
||||
workspace: git-auth
|
||||
- name: prefetch-dependencies
|
||||
params:
|
||||
- name: input
|
||||
value: $(params.prefetch-input)
|
||||
runAfter:
|
||||
- clone-repository
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: prefetch-dependencies
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.prefetch-input)
|
||||
operator: notin
|
||||
values:
|
||||
- ""
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: workspace
|
||||
- name: git-basic-auth
|
||||
workspace: git-auth
|
||||
- name: netrc
|
||||
workspace: netrc
|
||||
- name: build-container
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(params.output-image)
|
||||
- name: DOCKERFILE
|
||||
value: $(params.dockerfile)
|
||||
- name: CONTEXT
|
||||
value: $(params.path-context)
|
||||
- name: HERMETIC
|
||||
value: $(params.hermetic)
|
||||
- name: PREFETCH_INPUT
|
||||
value: $(params.prefetch-input)
|
||||
- name: IMAGE_EXPIRES_AFTER
|
||||
value: $(params.image-expires-after)
|
||||
- name: COMMIT_SHA
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
- name: BUILD_ARGS
|
||||
value:
|
||||
- $(params.build-args[*])
|
||||
- name: BUILD_ARGS_FILE
|
||||
value: $(params.build-args-file)
|
||||
runAfter:
|
||||
- prefetch-dependencies
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: buildah
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: workspace
|
||||
- name: build-image-index
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(params.output-image)
|
||||
- name: COMMIT_SHA
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
- name: IMAGE_EXPIRES_AFTER
|
||||
value: $(params.image-expires-after)
|
||||
- name: ALWAYS_BUILD_INDEX
|
||||
value: $(params.build-image-index)
|
||||
- name: IMAGES
|
||||
value:
|
||||
- $(tasks.build-container.results.IMAGE_URL)@$(tasks.build-container.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-container
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: build-image-index
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
- name: build-source-image
|
||||
params:
|
||||
- name: BINARY_IMAGE
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: BINARY_IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: source-build
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
- input: $(params.build-source-image)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: sast-shell-check
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-shell-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:4a63982791a1a68f560c486f524ef5b9fdbeee0c16fe079eee3181a2cfd1c1bf
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: sast-unicode-check
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-unicode-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.3@sha256:bec18fa5e82e801c3f267f29bf94535a5024e72476f2b27cca7271d506abb5ad
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: deprecated-base-image-check
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: deprecated-image-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: clair-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: clair-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: ecosystem-cert-preflight-checks
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: ecosystem-cert-preflight-checks
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: sast-snyk-check
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-snyk-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:351f2dce893159b703e9b6d430a2450b3df9967cb9bd3adb46852df8ccfe4c0d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: clamav-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: clamav-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: apply-tags
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: apply-tags
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: push-dockerfile
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: DOCKERFILE
|
||||
value: $(params.dockerfile)
|
||||
- name: CONTEXT
|
||||
value: $(params.path-context)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: push-dockerfile
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: rpms-signature-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: rpms-signature-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:1b6c20ab3dbfb0972803d3ebcb2fa72642e59400c77bd66dfd82028bdd09e120
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
- name: git-auth
|
||||
optional: true
|
||||
- name: netrc
|
||||
optional: true
|
||||
taskRunTemplate:
|
||||
serviceAccountName: build-pipeline-image-builder-frontend
|
||||
workspaces:
|
||||
- name: workspace
|
||||
volumeClaimTemplate:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
status: {}
|
||||
- name: git-auth
|
||||
secret:
|
||||
secretName: '{{ git_auth_secret }}'
|
||||
status: {}
|
||||
|
|
@ -1,555 +0,0 @@
|
|||
apiVersion: tekton.dev/v1
|
||||
kind: PipelineRun
|
||||
metadata:
|
||||
annotations:
|
||||
build.appstudio.openshift.io/repo: https://github.com/osbuild/image-builder-frontend?rev={{revision}}
|
||||
build.appstudio.redhat.com/commit_sha: '{{revision}}'
|
||||
build.appstudio.redhat.com/target_branch: '{{target_branch}}'
|
||||
pipelinesascode.tekton.dev/max-keep-runs: "3"
|
||||
pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch == "main"
|
||||
creationTimestamp:
|
||||
labels:
|
||||
appstudio.openshift.io/application: insights-image-builder
|
||||
appstudio.openshift.io/component: image-builder-frontend
|
||||
pipelines.appstudio.openshift.io/type: build
|
||||
name: image-builder-frontend-on-push
|
||||
namespace: insights-management-tenant
|
||||
spec:
|
||||
params:
|
||||
- name: git-url
|
||||
value: '{{source_url}}'
|
||||
- name: revision
|
||||
value: '{{revision}}'
|
||||
- name: output-image
|
||||
value: quay.io/redhat-user-workloads/insights-management-tenant/insights-image-builder/image-builder-frontend:{{revision}}
|
||||
- name: dockerfile
|
||||
value: build-tools/Dockerfile
|
||||
- name: path-context
|
||||
value: .
|
||||
pipelineSpec:
|
||||
description: |
|
||||
This pipeline is ideal for building container images from a Containerfile while reducing network traffic.
|
||||
|
||||
_Uses `buildah` to create a container image. It also optionally creates a source image and runs some build-time tests. EC will flag a violation for [`trusted_task.trusted`](https://enterprisecontract.dev/docs/ec-policies/release_policy.html#trusted_task__trusted) if any tasks are added to the pipeline.
|
||||
This pipeline is pushed as a Tekton bundle to [quay.io](https://quay.io/repository/konflux-ci/tekton-catalog/pipeline-docker-build?tab=tags)_
|
||||
finally:
|
||||
- name: show-sbom
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: show-sbom
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:beb0616db051952b4b861dd8c3e00fa1c0eccbd926feddf71194d3bb3ace9ce7
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: show-summary
|
||||
params:
|
||||
- name: pipelinerun-name
|
||||
value: $(context.pipelineRun.name)
|
||||
- name: git-url
|
||||
value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit)
|
||||
- name: image-url
|
||||
value: $(params.output-image)
|
||||
- name: build-task-status
|
||||
value: $(tasks.build-image-index.status)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: summary
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:3f6e8513cbd70f0416eb6c6f2766973a754778526125ff33d8e3633def917091
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
params:
|
||||
- description: Source Repository URL
|
||||
name: git-url
|
||||
type: string
|
||||
- default: ""
|
||||
description: Revision of the Source Repository
|
||||
name: revision
|
||||
type: string
|
||||
- description: Fully Qualified Output Image
|
||||
name: output-image
|
||||
type: string
|
||||
- default: .
|
||||
description: Path to the source code of an application's component from where to build image.
|
||||
name: path-context
|
||||
type: string
|
||||
- default: Dockerfile
|
||||
description: Path to the Dockerfile inside the context specified by parameter path-context
|
||||
name: dockerfile
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Force rebuild image
|
||||
name: rebuild
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Skip checks against built image
|
||||
name: skip-checks
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Execute the build with network isolation
|
||||
name: hermetic
|
||||
type: string
|
||||
- default: ""
|
||||
description: Build dependencies to be prefetched by Cachi2
|
||||
name: prefetch-input
|
||||
type: string
|
||||
- default: ""
|
||||
description: Image tag expiration time, time values could be something like 1h, 2d, 3w for hours, days, and weeks, respectively.
|
||||
name: image-expires-after
|
||||
- default: "false"
|
||||
description: Build a source image.
|
||||
name: build-source-image
|
||||
type: string
|
||||
- default: "false"
|
||||
description: Add built image into an OCI image index
|
||||
name: build-image-index
|
||||
type: string
|
||||
- default: []
|
||||
description: Array of --build-arg values ("arg=value" strings) for buildah
|
||||
name: build-args
|
||||
type: array
|
||||
- default: ""
|
||||
description: Path to a file with build arguments for buildah, see https://www.mankier.com/1/buildah-build#--build-arg-file
|
||||
name: build-args-file
|
||||
type: string
|
||||
results:
|
||||
- description: ""
|
||||
name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- description: ""
|
||||
name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- description: ""
|
||||
name: CHAINS-GIT_URL
|
||||
value: $(tasks.clone-repository.results.url)
|
||||
- description: ""
|
||||
name: CHAINS-GIT_COMMIT
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
tasks:
|
||||
- name: init
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(params.output-image)
|
||||
- name: rebuild
|
||||
value: $(params.rebuild)
|
||||
- name: skip-checks
|
||||
value: $(params.skip-checks)
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: init
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:08e18a4dc5f947c1d20e8353a19d013144bea87b72f67236b165dd4778523951
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: clone-repository
|
||||
params:
|
||||
- name: url
|
||||
value: $(params.git-url)
|
||||
- name: revision
|
||||
value: $(params.revision)
|
||||
runAfter:
|
||||
- init
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: git-clone
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:7939000e2f92fc8b5d2c4ee4ba9000433c5aa7700d2915a1d4763853d5fd1fd4
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: output
|
||||
workspace: workspace
|
||||
- name: basic-auth
|
||||
workspace: git-auth
|
||||
- name: prefetch-dependencies
|
||||
params:
|
||||
- name: input
|
||||
value: $(params.prefetch-input)
|
||||
runAfter:
|
||||
- clone-repository
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: prefetch-dependencies
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.2@sha256:ce5f2485d759221444357fe38276be876fc54531651e50dcfc0f84b34909d760
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.prefetch-input)
|
||||
operator: notin
|
||||
values:
|
||||
- ""
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: workspace
|
||||
- name: git-basic-auth
|
||||
workspace: git-auth
|
||||
- name: netrc
|
||||
workspace: netrc
|
||||
- name: build-container
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(params.output-image)
|
||||
- name: DOCKERFILE
|
||||
value: $(params.dockerfile)
|
||||
- name: CONTEXT
|
||||
value: $(params.path-context)
|
||||
- name: HERMETIC
|
||||
value: $(params.hermetic)
|
||||
- name: PREFETCH_INPUT
|
||||
value: $(params.prefetch-input)
|
||||
- name: IMAGE_EXPIRES_AFTER
|
||||
value: $(params.image-expires-after)
|
||||
- name: COMMIT_SHA
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
- name: BUILD_ARGS
|
||||
value:
|
||||
- $(params.build-args[*])
|
||||
- name: BUILD_ARGS_FILE
|
||||
value: $(params.build-args-file)
|
||||
runAfter:
|
||||
- prefetch-dependencies
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: buildah
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.4@sha256:7782cb7462130de8e8839a58dd15ed78e50938d718b51375267679c6044b4367
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: workspace
|
||||
- name: build-image-index
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(params.output-image)
|
||||
- name: COMMIT_SHA
|
||||
value: $(tasks.clone-repository.results.commit)
|
||||
- name: IMAGE_EXPIRES_AFTER
|
||||
value: $(params.image-expires-after)
|
||||
- name: ALWAYS_BUILD_INDEX
|
||||
value: $(params.build-image-index)
|
||||
- name: IMAGES
|
||||
value:
|
||||
- $(tasks.build-container.results.IMAGE_URL)@$(tasks.build-container.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-container
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: build-image-index
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-build-image-index:0.1@sha256:72f77a8c62f9d6f69ab5c35170839e4b190026e6cc3d7d4ceafa7033fc30ad7b
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
- name: build-source-image
|
||||
params:
|
||||
- name: BINARY_IMAGE
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: BINARY_IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: source-build
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.3@sha256:96ed9431854ecf9805407dca77b063abdf7aba1b3b9d1925a5c6145c6b7e95fd
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(tasks.init.results.build)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
- input: $(params.build-source-image)
|
||||
operator: in
|
||||
values:
|
||||
- "true"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: sast-shell-check
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-shell-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-shell-check:0.1@sha256:4a63982791a1a68f560c486f524ef5b9fdbeee0c16fe079eee3181a2cfd1c1bf
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: sast-unicode-check
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-unicode-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-unicode-check:0.3@sha256:bec18fa5e82e801c3f267f29bf94535a5024e72476f2b27cca7271d506abb5ad
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: deprecated-base-image-check
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: deprecated-image-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.5@sha256:1d07d16810c26713f3d875083924d93697900147364360587ccb5a63f2c31012
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: clair-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: clair-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:893ffa3ce26b061e21bb4d8db9ef7ed4ddd4044fe7aa5451ef391034da3ff759
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: ecosystem-cert-preflight-checks
|
||||
params:
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: ecosystem-cert-preflight-checks
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-ecosystem-cert-preflight-checks:0.2@sha256:1f151e00f7fc427654b7b76045a426bb02fe650d192ffe147a304d2184787e38
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: sast-snyk-check
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: sast-snyk-check
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.4@sha256:351f2dce893159b703e9b6d430a2450b3df9967cb9bd3adb46852df8ccfe4c0d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: clamav-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: clamav-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.3@sha256:cce2dfcc5bd6e91ee54aacdadad523b013eeae5cdaa7f6a4624b8cbcc040f439
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
- name: apply-tags
|
||||
params:
|
||||
- name: IMAGE_URL
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: apply-tags
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-apply-tags:0.2@sha256:70881c97a4c51ee1f4d023fa1110e0bdfcfd2f51d9a261fa543c3862b9a4eee9
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
- name: push-dockerfile
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
- name: IMAGE_DIGEST
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: DOCKERFILE
|
||||
value: $(params.dockerfile)
|
||||
- name: CONTEXT
|
||||
value: $(params.path-context)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: push-dockerfile
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-push-dockerfile:0.1@sha256:d5cb22a833be51dd72a872cac8bfbe149e8ad34da7cb48a643a1e613447a1f9d
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
workspaces:
|
||||
- name: workspace
|
||||
workspace: workspace
|
||||
- name: rpms-signature-scan
|
||||
params:
|
||||
- name: image-digest
|
||||
value: $(tasks.build-image-index.results.IMAGE_DIGEST)
|
||||
- name: image-url
|
||||
value: $(tasks.build-image-index.results.IMAGE_URL)
|
||||
runAfter:
|
||||
- build-image-index
|
||||
taskRef:
|
||||
params:
|
||||
- name: name
|
||||
value: rpms-signature-scan
|
||||
- name: bundle
|
||||
value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:1b6c20ab3dbfb0972803d3ebcb2fa72642e59400c77bd66dfd82028bdd09e120
|
||||
- name: kind
|
||||
value: task
|
||||
resolver: bundles
|
||||
when:
|
||||
- input: $(params.skip-checks)
|
||||
operator: in
|
||||
values:
|
||||
- "false"
|
||||
workspaces:
|
||||
- name: workspace
|
||||
- name: git-auth
|
||||
optional: true
|
||||
- name: netrc
|
||||
optional: true
|
||||
taskRunTemplate:
|
||||
serviceAccountName: build-pipeline-image-builder-frontend
|
||||
workspaces:
|
||||
- name: workspace
|
||||
volumeClaimTemplate:
|
||||
metadata:
|
||||
creationTimestamp:
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
status: {}
|
||||
- name: git-auth
|
||||
secret:
|
||||
secretName: '{{ git_auth_secret }}'
|
||||
status: {}
|
||||
27
.travis.yml
Normal file
27
.travis.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
language: node_js
|
||||
sudo: required
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- stage-stable
|
||||
- prod-beta
|
||||
- prod-stable
|
||||
notifications:
|
||||
email: false
|
||||
node_js:
|
||||
- '16'
|
||||
install:
|
||||
- npm ci
|
||||
before_script: |
|
||||
npm run api && [ -z "$(git status --porcelain=v1 2>/dev/null)" ] && echo "✓ No manual API changes." || echo "✗ API manually changed, please refer to the README for the procedure to follow for programmatically generated API endpoints." && [ -z "$(git status --porcelain=v1 2>/dev/null)" ]
|
||||
script:
|
||||
- NODE_ENV=production npm run build
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npx codecov
|
||||
after_success:
|
||||
- curl -sSL https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master/src/bootstrap.sh | bash -s
|
||||
env:
|
||||
global:
|
||||
- DEPLOY_REPO="git@github.com:RedHatInsights/image-builder-frontend-build"
|
||||
- NODE_OPTIONS="--max-old-space-size=4096 --max_old_space_size=4096"
|
||||
75
.travis/Jenkinsfile
vendored
Normal file
75
.travis/Jenkinsfile
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
@Library("github.com/RedHatInsights/insights-pipeline-lib@v3")
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
node {
|
||||
stage ("deploy") {
|
||||
|
||||
checkout scm
|
||||
withCredentials(bindings: [sshUserPrivateKey(credentialsId: "cloud-netstorage",
|
||||
keyFileVariable: "privateKeyFile",
|
||||
passphraseVariable: "",
|
||||
usernameVariable: "")]) {
|
||||
|
||||
String APP_NAME = "__APP_NAME__"
|
||||
String BRANCH = env.BRANCH_NAME.replaceAll("origin/", "")
|
||||
|
||||
if (BRANCH == "prod-stable") {
|
||||
PREFIX = ""
|
||||
} else if (BRANCH == "prod-beta") {
|
||||
PREFIX = "beta/"
|
||||
} else if (BRANCH == "qa-stable" || BRANCH == "stage-stable") {
|
||||
PREFIX = "stage/"
|
||||
} else if (BRANCH == "qa-beta" || BRANCH == "stage-beta") {
|
||||
PREFIX = "stage/beta/"
|
||||
} else {
|
||||
error "Bug: invalid branch name, we only support (prod/qa/stage)-(beta/stable) and we got ${BRANCH}"
|
||||
}
|
||||
|
||||
// Write build info into app.info.json
|
||||
// We have the src info there already
|
||||
def app_info = readJSON file: "./app.info.json"
|
||||
app_info.build_branch = BRANCH
|
||||
app_info.build_hash = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
|
||||
app_info.build_id = env.BUILD_ID
|
||||
writeJSON file: "./app.info.json", json: app_info
|
||||
|
||||
// Send Slack Notification
|
||||
String SLACK_TEXT = "${APP_NAME}/${BRANCH} [STATUS] - Deploy build ${app_info.build_id} started for GIT COMMIT ${app_info.build_hash}."
|
||||
slackSend message: SLACK_TEXT, color: 'black', channel: '#insights-bots'
|
||||
|
||||
AKAMAI_BASE_PATH = "822386"
|
||||
AKAMAI_APP_PATH = "/${AKAMAI_BASE_PATH}/${PREFIX}apps/${APP_NAME}"
|
||||
|
||||
sh """
|
||||
eval `ssh-agent`
|
||||
ssh-add \"$privateKeyFile\"
|
||||
chmod 600 ~/.ssh/known_hosts ~/.ssh/config
|
||||
n=0
|
||||
until [ \$n -ge 10 ]
|
||||
do
|
||||
rsync -arv -e \"ssh -2 -o StrictHostKeyChecking=no\" * sshacs@cloud-unprotected.upload.akamai.com:${AKAMAI_APP_PATH} && break
|
||||
n=\$[\$n+1]
|
||||
sleep 10
|
||||
done
|
||||
"""
|
||||
|
||||
//Clear the cache for the app being deployed
|
||||
openShiftUtils.withJnlpNode(
|
||||
image: "quay.io/redhatqe/origin-jenkins-agent-akamai:4.9",
|
||||
namespace: "insights-dev-jenkins"
|
||||
) {
|
||||
//install python dependencies
|
||||
sh "wget https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master/src/akamai_cache_buster/bustCache.py"
|
||||
sh "wget https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master/src/akamai_cache_buster/requirements.txt"
|
||||
sh "pip install -r requirements.txt"
|
||||
withCredentials([file(credentialsId: "jenkins-eccu-cache-purge", variable: 'EDGERC')]) {
|
||||
//path to .edgerc file is now set to $EDGERC"
|
||||
//Bust the current cache
|
||||
sh "python3 bustCache.py $EDGERC ${APP_NAME} ${BRANCH}"
|
||||
}
|
||||
// Trigger IQE pipelines
|
||||
sh ("curl -X POST -H 'Content-type: application/json' --data '{\"text\":\"Trigger IQE pipelines\"}' WEBHOOK_PLACEHOLDER")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
.travis/custom_release.sh
Executable file
26
.travis/custom_release.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
if [ "${TRAVIS_BRANCH}" = "main" ]; then
|
||||
.travis/release.sh "stage-beta"
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_BRANCH}" = "stage-stable" ]; then
|
||||
# Download modified Jenkinsfile
|
||||
curl -o .travis/58231b16fdee45a03a4ee3cf94a9f2c3 https://raw.githubusercontent.com/RedHatInsights/image-builder-frontend/stage-stable/.travis/Jenkinsfile
|
||||
# Insert stage webhook URL
|
||||
sed -i 's|WEBHOOK_PLACEHOLDER|https://smee.io/IQDT9yRXsWlqbxpg|g' .travis/58231b16fdee45a03a4ee3cf94a9f2c3
|
||||
.travis/release.sh "stage-stable"
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_BRANCH}" = "prod-beta" ]; then
|
||||
.travis/release.sh "prod-beta"
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_BRANCH}" = "prod-stable" ]; then
|
||||
# Download modified Jenkinsfile
|
||||
curl -o .travis/58231b16fdee45a03a4ee3cf94a9f2c3 https://raw.githubusercontent.com/RedHatInsights/image-builder-frontend/stage-stable/.travis/Jenkinsfile
|
||||
# Insert prod webhook URL
|
||||
sed -i 's|WEBHOOK_PLACEHOLDER|https://smee.io/F9gZwIGELxwah4if|g' .travis/58231b16fdee45a03a4ee3cf94a9f2c3
|
||||
.travis/release.sh "prod-stable"
|
||||
fi
|
||||
BIN
.travis/deploy_key.enc
Normal file
BIN
.travis/deploy_key.enc
Normal file
Binary file not shown.
111
Makefile
111
Makefile
|
|
@ -1,111 +0,0 @@
|
|||
PACKAGE_NAME = cockpit-image-builder
|
||||
INSTALL_DIR_BASE = /share/cockpit/
|
||||
INSTALL_DIR = $(INSTALL_DIR_BASE)$(PACKAGE_NAME)
|
||||
APPSTREAMFILE=org.image-builder.$(PACKAGE_NAME).metainfo.xml
|
||||
|
||||
VERSION := $(shell (cd "$(SRCDIR)" && grep "^Version:" cockpit/$(PACKAGE_NAME).spec | sed 's/[^[:digit:]]*\([[:digit:]]\+\).*/\1/'))
|
||||
COMMIT = $(shell (cd "$(SRCDIR)" && git rev-parse HEAD))
|
||||
|
||||
# TODO: figure out a strategy for keeping this updated
|
||||
COCKPIT_REPO_COMMIT = a70142a7a6f9c4e78e71f3c4ec738b6db2fbb04f
|
||||
COCKPIT_REPO_URL = https://github.com/cockpit-project/cockpit.git
|
||||
COCKPIT_REPO_TREE = '$(strip $(COCKPIT_REPO_COMMIT))^{tree}'
|
||||
|
||||
# checkout common files from Cockpit repository required to build this project;
|
||||
# this has no API stability guarantee, so check out a stable tag when you start
|
||||
# a new project, use the latest release, and update it from time to time
|
||||
COCKPIT_REPO_FILES = \
|
||||
pkg/lib \
|
||||
$(NULL)
|
||||
|
||||
help:
|
||||
@cat Makefile
|
||||
|
||||
#
|
||||
# Install target for specfile
|
||||
#
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
$(MAKE) cockpit/install
|
||||
|
||||
#
|
||||
# Cockpit related targets
|
||||
#
|
||||
|
||||
.PHONY: cockpit/clean
|
||||
cockpit/clean:
|
||||
rm -f cockpit/public/*.css
|
||||
rm -f cockpit/public/*.js
|
||||
|
||||
.PHONY: cockpit/install
|
||||
cockpit/install:
|
||||
mkdir -p $(DESTDIR)$(PREFIX)$(INSTALL_DIR)
|
||||
cp -a cockpit/public/* $(DESTDIR)$(PREFIX)$(INSTALL_DIR)
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/share/metainfo
|
||||
msgfmt --xml -d po \
|
||||
--template cockpit/public/$(APPSTREAMFILE) \
|
||||
-o $(DESTDIR)$(PREFIX)/share/metainfo/$(APPSTREAMFILE)
|
||||
|
||||
.PHONY: cockpit/devel-uninstall
|
||||
cockpit/devel-uninstall: PREFIX=~/.local
|
||||
cockpit/devel-uninstall:
|
||||
rm -rf $(PREFIX)$(INSTALL_DIR)
|
||||
|
||||
.PHONY: cockpit/devel-install
|
||||
cockpit/devel-install: PREFIX=~/.local
|
||||
cockpit/devel-install:
|
||||
PREFIX="~/.local"
|
||||
mkdir -p $(PREFIX)$(INSTALL_DIR_BASE)
|
||||
ln -s $(shell pwd)/cockpit/public $(PREFIX)$(INSTALL_DIR)
|
||||
|
||||
.PHONY: cockpit/download
|
||||
cockpit/download: Makefile
|
||||
@git rev-list --quiet --objects $(COCKPIT_REPO_TREE) -- 2>/dev/null || \
|
||||
git fetch --no-tags --no-write-fetch-head --depth=1 $(COCKPIT_REPO_URL) $(COCKPIT_REPO_COMMIT)
|
||||
git archive $(COCKPIT_REPO_TREE) -- $(COCKPIT_REPO_FILES) | tar x
|
||||
|
||||
.PHONY: cockpit/build
|
||||
cockpit/build: cockpit/download
|
||||
npm run build:cockpit
|
||||
|
||||
.PHONY: cockpit/devel
|
||||
cockpit/devel: cockpit/devel-uninstall cockpit/build cockpit/devel-install
|
||||
|
||||
#
|
||||
# Building packages
|
||||
#
|
||||
|
||||
RPM_SPEC=cockpit/$(PACKAGE_NAME).spec
|
||||
NODE_MODULES_TEST=package-lock.json
|
||||
TARFILE=$(PACKAGE_NAME)-$(VERSION).tar.gz
|
||||
|
||||
$(RPM_SPEC): $(RPM_SPEC) $(NODE_MODULES_TEST)
|
||||
provides=$$(npm ls --omit dev --package-lock-only --depth=Infinity | grep -Eo '[^[:space:]]+@[^[:space:]]+' | sort -u | sed 's/^/Provides: bundled(npm(/; s/\(.*\)@/\1)) = /'); \
|
||||
awk -v p="$$provides" '{gsub(/%{VERSION}/, "$(VERSION)"); $(SUB_NODE_ENV) gsub(/%{NPM_PROVIDES}/, p)}1' $< > $@
|
||||
|
||||
$(TARFILE): export NODE_ENV ?= production
|
||||
$(TARFILE): cockpit/build
|
||||
touch -r package.json package-lock.json
|
||||
touch cockpit/public/*
|
||||
tar czf $(TARFILE) --transform 's,^,$(PACKAGE_NAME)/,' \
|
||||
--exclude node_modules \
|
||||
$$(git ls-files) $(RPM_SPEC) $(NODE_MODULES_TEST) cockpit/public/ cockpit/README.md
|
||||
realpath $(TARFILE)
|
||||
|
||||
dist: $(TARFILE)
|
||||
@ls -1 $(TARFILE)
|
||||
|
||||
.PHONY: srpm
|
||||
srpm: $(TARFILE)
|
||||
rpmbuild -bs \
|
||||
--define "_sourcedir `pwd`" \
|
||||
--define "_topdir $(CURDIR)/rpmbuild" \
|
||||
$(RPM_SPEC)
|
||||
|
||||
.PHONY: rpm
|
||||
rpm: $(TARFILE)
|
||||
rpmbuild -bb \
|
||||
--define "_sourcedir `pwd`" \
|
||||
--define "_topdir $(CURDIR)/rpmbuild" \
|
||||
$(RPM_SPEC)
|
||||
291
README.md
291
README.md
|
|
@ -1,38 +1,15 @@
|
|||
# Image Builder Frontend
|
||||
|
||||
Frontend code for Image Builder.
|
||||
|
||||
## Project
|
||||
|
||||
* **Website**: https://www.osbuild.org
|
||||
* **Bug Tracker**: https://github.com/osbuild/image-builder-frontend/issues
|
||||
* **Discussions**: https://github.com/orgs/osbuild/discussions
|
||||
* **Matrix**: #image-builder on [fedoraproject.org](https://matrix.to/#/#image-builder:fedoraproject.org)
|
||||
|
||||
## Principles
|
||||
|
||||
1. We want to use the latest and greatest web technologies.
|
||||
2. We want to expose all the options and customizations possible, even if not all are visible by default.
|
||||
3. The default path should be ‘short(est)’ clickpath, which should be determined in a data-driven way.
|
||||
4. This is an [Insights application](https://github.com/RedHatInsights/), so it abides by some rules and standards of Insights.
|
||||
# image-builder-frontend
|
||||
|
||||
## Table of Contents
|
||||
1. [How to build and run image-builder-frontend](#frontend-development)
|
||||
1. [Frontend Development](#frontend-development)
|
||||
2. [Image builder as Cockpit plugin](#image-builder-as-cockpit-plugin)
|
||||
3. [Backend Development](#backend-development)
|
||||
2. [API](#api-endpoints)
|
||||
3. [Unleash feature flags](#unleash-feature-flags)
|
||||
4. [File structure](#file-structure)
|
||||
5. [Style Guidelines](#style-guidelines)
|
||||
6. [Test Guidelines](#test-guidelines)
|
||||
7. [Running hosted service Playwright tests](#running-hosted-service-playwright-tests)
|
||||
2. [Backend Development](#backend-development)
|
||||
2. [File structure](#file-structure)
|
||||
3. [Style Guidelines](#style-guidelines)
|
||||
4. [Test Guidelines](#test-guidelines)
|
||||
|
||||
## How to build and run image-builder-frontend
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Running image-builder-frontend against [console.redhat.com](https://console.redhat.com/) requires connection to the Red Hat VPN, which is only available to Red Hat employees. External contributors can locally run [image builder as Cockpit plugin](#image-builder-as-cockpit-plugin).
|
||||
|
||||
### Frontend Development
|
||||
|
||||
To develop the frontend you can use a proxy to run image-builder-frontend locally
|
||||
|
|
@ -43,15 +20,16 @@ worrying if a feature from stage has been released yet.
|
|||
|
||||
#### Nodejs and npm version
|
||||
|
||||
Make sure you have npm@10 and node 22+ installed. If you need multiple versions of nodejs check out [nvm](https://github.com/nvm-sh/nvm).
|
||||
Make sure you have npm@7 and node 15+ installed. If you need multiple versions of nodejs check out [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
#### Webpack proxy
|
||||
|
||||
1. run `npm ci`
|
||||
|
||||
2. run `npm run start:prod`
|
||||
2. run `npm run prod-beta`. This command uses a prod-beta env by default. Configure your
|
||||
environment by the `env` attribute in `dev.webpack.config.js`.
|
||||
|
||||
3. redirect `prod.foo.redhat.com` to localhost, if this has not been done already
|
||||
3. Secondly redirect a few `prod.foo.redhat.com` to localhost, if this has not been done already.
|
||||
|
||||
```bash
|
||||
echo "127.0.0.1 prod.foo.redhat.com" >> /etc/hosts
|
||||
|
|
@ -63,9 +41,10 @@ echo "127.0.0.1 prod.foo.redhat.com" >> /etc/hosts
|
|||
|
||||
1. run `npm ci`
|
||||
|
||||
2. run `npm run start:stage`
|
||||
2. run `npm run stage-beta`. This command uses a stage-beta env by default. Configure your
|
||||
environment by the `env` attribute in `dev.webpack.config.js`.
|
||||
|
||||
3. redirect `stage.foo.redhat.com` to localhost, if this has not been done already
|
||||
3. Secondly redirect a few `stage.foo.redhat.com` to localhost, if this has not been done already.
|
||||
|
||||
```bash
|
||||
echo "127.0.0.1 stage.foo.redhat.com" >> /etc/hosts
|
||||
|
|
@ -73,73 +52,51 @@ echo "127.0.0.1 stage.foo.redhat.com" >> /etc/hosts
|
|||
|
||||
4. open browser at `https://stage.foo.redhat.com:1337/beta/insights/image-builder`
|
||||
|
||||
### Image builder as Cockpit plugin
|
||||
#### Insights proxy (deprecated)
|
||||
|
||||
> [!NOTE]
|
||||
> Issues marked with [cockpit-image-builder](https://github.com/osbuild/image-builder-frontend/issues?q=is%3Aissue%20state%3Aopen%20label%3Acockpit-image-builder) label are reproducible in image builder plugin and can be worked on by external contributors without connection to the Red Hat VPN.
|
||||
1. Clone the insights proxy: https://github.com/RedHatInsights/insights-proxy
|
||||
|
||||
#### Cockpit setup
|
||||
To install and setup Cockpit follow guide at: https://cockpit-project.org/running.html
|
||||
2. Setting up the proxy
|
||||
|
||||
#### On-premises image builder installation and configuration
|
||||
To install and configure `osbuild-composer` on your local machine follow our documentation: https://osbuild.org/docs/on-premises/installation/
|
||||
Choose a runner (podman or docker), and point the SPANDX_CONFIG variable to
|
||||
`profile/local-frontend.js` included in image-builder-frontend.
|
||||
|
||||
#### Scripts for local development of image builder plugin
|
||||
```
|
||||
sudo insights-proxy/scripts/patch-etc-hosts.sh
|
||||
export RUNNER="podman"
|
||||
export SPANDX_CONFIG=$PATH_TO/image-builder-frontend/profiles/local-frontend.js
|
||||
sudo -E insights-proxy/scripts/run.sh
|
||||
```
|
||||
|
||||
The following scripts are used to build the frontend with Webpack and install it into the Cockpit directories. These scripts streamline the development process by automating build and installation steps.
|
||||
3. Starting up image-builder-frontend
|
||||
|
||||
Runs Webpack with the specified configuration (cockpit/webpack.config.ts) to build the frontend assets.
|
||||
Use this command whenever you need to compile the latest changes in your frontend code.
|
||||
In the image-builder-frontend checkout directory
|
||||
|
||||
Creates the necessary directory in the user's local Cockpit share (~/.local/share/cockpit/).
|
||||
Creates a symbolic link (image-builder-frontend) pointing to the built frontend assets (cockpit/public).
|
||||
Use this command after building the frontend to install it locally for development purposes.
|
||||
The symbolic link allows Cockpit to serve the frontend assets from your local development environment,
|
||||
making it easier to test changes in real-time without deploying to a remote server.
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
```bash
|
||||
make cockpit/build
|
||||
```
|
||||
The UI should be running on
|
||||
https://prod.foo.redhat.com:1337/beta/insights/image-builder/landing.
|
||||
Note that this requires you to have access to either production or stage (plus VPN and proxy config) of insights.
|
||||
|
||||
```bash
|
||||
make cockpit/devel-install
|
||||
```
|
||||
#### API endpoints
|
||||
|
||||
To uninstall and remove the symbolic link, run the following command:
|
||||
API endpoints are programmatically generated with the RTKQ library. This
|
||||
sections overview the steps to add new APIs and endpoints.
|
||||
|
||||
```bash
|
||||
make cockpit/devel-uninstall
|
||||
```
|
||||
##### Add a new API
|
||||
|
||||
For convenience, you can run the following to combine all three steps:
|
||||
For an hypothetical API called foobar
|
||||
|
||||
1. Download the foobar api openapi json or yaml representation under
|
||||
`api/schema/foobar.json`
|
||||
|
||||
```bash
|
||||
make cockpit/devel
|
||||
```
|
||||
|
||||
### Backend Development
|
||||
|
||||
To develop both the frontend and the backend you can again use the proxy to run both the
|
||||
frontend and backend locally against the chrome at cloud.redhat.com. For instructions
|
||||
see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-getting-started).
|
||||
|
||||
## API endpoints
|
||||
|
||||
API slice definitions are programmatically generated using the [@rtk-query/codegen-openapi](https://redux-toolkit.js.org/rtk-query/usage/code-generation) package.
|
||||
|
||||
The OpenAPI schema are imported during code generation. OpenAPI configuration files are
|
||||
stored in `/api/config`. Each endpoint has a corresponding empty API slice and generated API
|
||||
slice which are stored in `/src/store`.
|
||||
|
||||
### Add a new API schema
|
||||
|
||||
For a hypothetical API called foobar
|
||||
|
||||
1. Create a new "empty" API file under `src/store/emptyFoobarApi.ts` that has following
|
||||
2. Create a new "empty" api file under `src/store/emptyFoobarApi.ts` that has for
|
||||
content:
|
||||
|
||||
```typescript
|
||||
```{ts}
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { FOOBAR_API } from '../constants';
|
||||
|
|
@ -147,26 +104,26 @@ import { FOOBAR_API } from '../constants';
|
|||
// initialize an empty api service that we'll inject endpoints into later as needed
|
||||
export const emptyFoobarApi = createApi({
|
||||
reducerPath: 'foobarApi',
|
||||
baseQuery: fetchBaseQuery({ baseUrl: window.location.origin + FOO_BAR }),
|
||||
baseQuery: fetchBaseQuery({ baseUrl: FOO_BAR }),
|
||||
endpoints: () => ({}),
|
||||
});
|
||||
```
|
||||
|
||||
2. Declare new constant `FOOBAR_API` with the API url in `src/constants.ts`
|
||||
3. Declare the new constat `FOOBAR_API` to the API url in `src/constants.js`
|
||||
|
||||
```typescript
|
||||
```
|
||||
export const FOOBAR_API = 'api/foobar/v1'
|
||||
```
|
||||
|
||||
3. Create the config file for code generation in `api/config/foobar.ts` containing:
|
||||
4. Create the config file for code generation in `api/config/foobar.ts` containing:
|
||||
|
||||
```typescript
|
||||
```
|
||||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'URL_TO_THE_OPENAPI_SCHEMA',
|
||||
schemaFile: '../schema/foobar.json',
|
||||
apiFile: '../../src/store/emptyFoobarApi.ts',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
apiImport: 'emptyEdgeApi',
|
||||
outputFile: '../../src/store/foobarApi.ts',
|
||||
exportName: 'foobarApi',
|
||||
hooks: true,
|
||||
|
|
@ -174,29 +131,34 @@ const config: ConfigFile = {
|
|||
};
|
||||
```
|
||||
|
||||
4. Update the `eslint.config.js` file by adding the generated code path to the ignores array:
|
||||
5. Update the `api.sh` script by adding a new line for npx to generate the code:
|
||||
|
||||
```
|
||||
ignores: [
|
||||
<other ignored files>,
|
||||
'**/foobarApi.ts',
|
||||
]
|
||||
npx @rtk-query/codegen-openapi ./api/config/foobar.ts &
|
||||
```
|
||||
|
||||
5. run api generation
|
||||
|
||||
```bash
|
||||
6. Update the `.eslintignore` file by adding a new line for the generated code:
|
||||
|
||||
```
|
||||
foobarApi.ts
|
||||
```
|
||||
|
||||
7. run api generation
|
||||
|
||||
```
|
||||
npm run api
|
||||
```
|
||||
|
||||
And voilà!
|
||||
|
||||
### Add a new endpoint
|
||||
##### Add a new endpoint
|
||||
|
||||
To add a new endpoint, simply update the `api/config/foobar.ts` file with new
|
||||
endpoints in the `filterEndpoints` table.
|
||||
|
||||
## Unleash feature flags
|
||||
|
||||
#### Unleash feature flags for the frontend
|
||||
|
||||
Your user needs to have the corresponding rights, do the
|
||||
same as this MR in internal gitlab https://gitlab.cee.redhat.com/service/app-interface/-/merge_requests/79225
|
||||
|
|
@ -212,49 +174,47 @@ existing flags:
|
|||
|
||||
https://github.com/RedHatInsights/image-builder-frontend/blob/c84b493eba82ce83a7844943943d91112ffe8322/src/Components/ImagesTable/ImageLink.js#L99
|
||||
|
||||
### Mocking flags for tests
|
||||
##### Mocking flags for tests
|
||||
|
||||
Flags can be mocked for the unit tests to access some feature. Checkout:
|
||||
https://github.com/osbuild/image-builder-frontend/blob/9a464e416bc3769cfc8e23b62f1dd410eb0e0455/src/test/Components/CreateImageWizard/CreateImageWizard.test.tsx#L49
|
||||
https://github.com/RedHatInsights/image-builder-frontend/blob/c84b493eba82ce83a7844943943d91112ffe8322/src/test/Components/CreateImageWizard/CreateImageWizard.test.js#L74
|
||||
|
||||
If the two possible code path accessible via the toggles are defined in the code
|
||||
base, then it's good practice to test the two of them. If not, only test what's
|
||||
actually owned by the frontend project.
|
||||
|
||||
|
||||
### Cleaning the flags
|
||||
##### Cleaning the flags
|
||||
|
||||
Unleash toggles are expected to live for a limited amount of time, documentation
|
||||
specify 40 days for a release, we should keep that in mind for each toggle
|
||||
we're planning on using.
|
||||
|
||||
### Backend Development
|
||||
|
||||
To develop both the frontend and the backend you can again use the proxy to run both the
|
||||
frontend and backend locally against the chrome at cloud.redhat.com. For instructions
|
||||
see [devel/README.md](devel/README.md).
|
||||
|
||||
## File Structure
|
||||
|
||||
### Quick Reference
|
||||
| Directory | Description |
|
||||
| --------- | ----------- |
|
||||
| [`/api`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/api) | API schema and config files |
|
||||
| [`/config`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/config) | webpack configuration |
|
||||
| [`/devel`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/devel) | tools for local development |
|
||||
| [`/src`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src) | source code |
|
||||
| [`/src/Components`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/Components) | source code split by individual components |
|
||||
| [`/src/test`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/test) | test utilities |
|
||||
| [`/src/test/mocks`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/test/mocks) | mock handlers and server config for MSW |
|
||||
| [`/src/store`](https://github.com/RedHatInsights/image-builder-frontend/tree/main/src/store) | Redux store |
|
||||
| [`/src/api.js`](https://github.com/RedHatInsights/image-builder-frontend/blob/main/src/api.js) | API calls |
|
||||
|
||||
## Style Guidelines
|
||||
|
||||
This project uses recommended rule sets rom several plugins:
|
||||
- `@eslint/js`
|
||||
- `typescript-eslint`
|
||||
- `eslint-plugin-react`
|
||||
- `eslint-plugin-react-hooks`
|
||||
- `eslint-plugin-react-redux`
|
||||
- `eslint-plugin-import`
|
||||
- `eslint-plugin-jsx-a11y`
|
||||
- `eslint-plugin-disable-autofix`
|
||||
- `eslint-plugin-jest-dom`
|
||||
- `eslint-plugin-testing-library`
|
||||
- `eslint-plugin-playwright`
|
||||
- `@redhat-cloud-services/eslint-config-redhat-cloud-services`
|
||||
This project uses eslint's recommended styling guidelines. These rules can be found here:
|
||||
https://eslint.org/docs/rules/
|
||||
|
||||
To run the linter, use:
|
||||
```bash
|
||||
|
|
@ -263,14 +223,20 @@ npm run lint
|
|||
|
||||
Any errors that can be fixed automatically, can be corrected by running:
|
||||
```bash
|
||||
npm run lint:js:fix
|
||||
npm run lint --fix
|
||||
```
|
||||
|
||||
All the linting rules and configuration of ESLint can be found in [`eslint.config.js`](https://github.com/RedHatInsights/image-builder-frontend/blob/main/eslint.config.js).
|
||||
All the linting rules and configuration of eslint can be found in [`.eslintrc.yml`](https://github.com/RedHatInsights/image-builder-frontend/blob/main/.eslintrc.yml).
|
||||
|
||||
### Additional eslint rules
|
||||
There are also additional rules added to enforce code style. Those being:
|
||||
- `import/order` -> enforces the order in import statements and separates them into groups based on their type
|
||||
- `prefer-const` -> enforces use of `const` declaration for variables that are never reassigned
|
||||
- `no-console` -> throws an error for any calls of `console` methods leftover after debugging
|
||||
|
||||
## Test Guidelines
|
||||
|
||||
This project is tested using the [Vitest](https://vitest.dev/guide/) framework, [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), and the [Mock Service Worker](https://mswjs.io/docs/) library.
|
||||
This project is tested using the [Jest](https://jestjs.io/docs/getting-started) framework, [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), and the [Mock Service Worker](https://mswjs.io/docs/) library.
|
||||
|
||||
All UI contributions must also include a new test or update an existing test in order to maintain code coverage.
|
||||
|
||||
|
|
@ -281,79 +247,18 @@ To run the unit tests, the linter, and the code coverage check run:
|
|||
npm run test
|
||||
```
|
||||
|
||||
These tests will also be run in our CI when a PR is opened.
|
||||
These tests will also be run in our Travis CI when a PR is opened.
|
||||
|
||||
Note that `testing-library` DOM printout is currently disabled for all tests by the following configuration in `src/test/setup.ts`:
|
||||
```typescript
|
||||
configure({
|
||||
getElementError: (message: string) => {
|
||||
const error = new Error(message);
|
||||
error.name = 'TestingLibraryElementError';
|
||||
error.stack = '';
|
||||
return error;
|
||||
},
|
||||
});
|
||||
## API endpoints
|
||||
|
||||
API slice definitions are generated using the [@rtk-query/codegen-openapi](https://redux-toolkit.js.org/rtk-query/usage/code-generation) package.
|
||||
|
||||
OpenAPI schema for the endpoints are stored in `/api/schema`. Their
|
||||
corresponding configuration files are stored in `/api/config`. Each endpoint
|
||||
has a corresponding empty API slice and generated API slice which are stored in
|
||||
`/src/store`.
|
||||
|
||||
To generate or update API slice definitions, run:
|
||||
```bash
|
||||
npm run api
|
||||
```
|
||||
If you'd like to see the stack printed out you can either temporarily disable the configuration or generate a [Testing Playground](https://testing-playground.com/) link by adding `screen.logTestingPlaygroundURL()` to your test.
|
||||
|
||||
### ~~Using MSW data in development~~ - CURRENTLY NOT WORKING
|
||||
|
||||
If you want to develop in environment with mocked data, run the command `npm run stage-beta:msw`.
|
||||
|
||||
#### Enabling MSW
|
||||
In a case you're seeing `Error: [MSW] Failed to register the Service Worker` in console, you might also need to configure SSL certification on your computer.
|
||||
|
||||
In order to do this install [mkcert](https://github.com/FiloSottile/mkcert)
|
||||
|
||||
After the installation, go to the `/node_modules/.cache/webpack-dev-server` folder and run following commands:
|
||||
|
||||
1. `mkcert -install` to create a new certificate authority on your machine
|
||||
2. `mkcert prod.foo.redhat.com` to create the actual signed certificate
|
||||
|
||||
#### Mac Configuration
|
||||
Follow these steps to find and paste the certification file into the 'Keychain Access' application:
|
||||
|
||||
1. Open the 'Keychain Access' application.
|
||||
|
||||
2. Select 'login' on the left side.
|
||||
|
||||
3. Navigate to the 'Certificates' tab.
|
||||
|
||||
4. Drag the certification file (located at /image-builder-frontend/node_modules/.cache/webpack-dev-server/server.pem) to the certification list.
|
||||
|
||||
5. Double-click on the added certificate (localhost certificate) to open the localhost window.
|
||||
|
||||
6. Open the 'Trust' dropdown menu.
|
||||
|
||||
7. Set all options to 'Always Trust'.
|
||||
|
||||
8. Close the localhost screen.
|
||||
|
||||
9. Run `npm run stage-beta:msw` and open the Firefox browser to verify that it is working as expected.
|
||||
|
||||
## Running hosted service Playwright tests
|
||||
|
||||
1. Copy the [example env file](playwright_example.env) content and create a file named `.env` in the root directory of the project. Paste the example file content into it.
|
||||
For local development fill in the:
|
||||
* `BASE_URL` - `https://stage.foo.redhat.com:1337` is required, which is already set in the example config
|
||||
* `PLAYWRIGHT_USER` - your consoledot stage username
|
||||
* `PLAYWRIGHT_PASSWORD` - your consoledot stage password
|
||||
|
||||
2. Make sure Playwright is installed as a dev dependency
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
3. Download the Playwright browsers with
|
||||
```bash
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
4. Start the local development stage server by running
|
||||
```bash
|
||||
npm run start:stage
|
||||
```
|
||||
|
||||
5. Now you have two options of how to run the tests:
|
||||
* (Preferred) Use VS Code and the [Playwright Test module for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). But other editors do have similar plugins for ease of use, if so desired
|
||||
* Using terminal - `npx playwright test` will run the playwright test suite. `npx playwright test --headed` will run the suite in a vnc-like browser so you can watch it's interactions.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ npx @rtk-query/codegen-openapi ./api/config/imageBuilder.ts &
|
|||
npx @rtk-query/codegen-openapi ./api/config/rhsm.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/contentSources.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/provisioning.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/compliance.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/composerCloudApi.ts &
|
||||
npx @rtk-query/codegen-openapi ./api/config/edge.ts &
|
||||
|
||||
# Wait for all background jobs to finish
|
||||
wait
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# API
|
||||
|
||||
This folder contains generated code for API endpoints needed by this repo.
|
||||
|
||||
## Updating API changes
|
||||
|
||||
To pull new API changes and regenerating the code run
|
||||
|
||||
```shell
|
||||
npm run api
|
||||
```
|
||||
|
||||
## Regenerating the code only
|
||||
|
||||
To regenerate the code only, run
|
||||
|
||||
```shell
|
||||
npm run api:generate
|
||||
```
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/compliance/v2/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyComplianceApi.ts',
|
||||
apiImport: 'emptyComplianceApi',
|
||||
outputFile: '../../src/store/service/complianceApi.ts',
|
||||
exportName: 'complianceApi',
|
||||
hooks: true,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: ['policies', 'policy'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/osbuild-composer/main/internal/cloudapi/v2/openapi.v2.yml',
|
||||
apiFile: '../../src/store/cockpit/emptyComposerCloudApi.ts',
|
||||
apiImport: 'emptyComposerCloudApi',
|
||||
outputFile: '../../src/store/cockpit/composerCloudApi.ts',
|
||||
exportName: 'composerCloudApi',
|
||||
hooks: false,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: ['postCompose', 'getComposeStatus'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,26 +1,13 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/content-sources/v1/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyContentSourcesApi.ts',
|
||||
schemaFile: '../schema/contentSources.json',
|
||||
apiFile: '../../src/store/emptyContentSourcesApi.ts',
|
||||
apiImport: 'emptyContentSourcesApi',
|
||||
outputFile: '../../src/store/service/contentSourcesApi.ts',
|
||||
outputFile: '../../src/store/contentSourcesApi.ts',
|
||||
exportName: 'contentSourcesApi',
|
||||
hooks: true,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: [
|
||||
'createRepository',
|
||||
'listRepositories',
|
||||
'listRepositoriesRpms',
|
||||
'listRepositoryParameters',
|
||||
'searchRpm',
|
||||
'searchPackageGroup',
|
||||
'listFeatures',
|
||||
'listSnapshotsByDate',
|
||||
'bulkImportRepositories',
|
||||
'listTemplates',
|
||||
'getTemplate',
|
||||
],
|
||||
filterEndpoints: ['listRepositories', 'listRepositoriesRpms'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
35
api/config/edge.ts
Normal file
35
api/config/edge.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: '../schema/edge.json',
|
||||
apiFile: '../../src/store/emptyEdgeApi.ts',
|
||||
apiImport: 'emptyEdgeApi',
|
||||
outputFile: '../../src/store/edgeApi.ts',
|
||||
exportName: 'edgeApi',
|
||||
hooks: true,
|
||||
filterEndpoints: [
|
||||
'createImage',
|
||||
'createImageUpdate',
|
||||
'getAllImages',
|
||||
'getImageStatusByID',
|
||||
'getImageByID',
|
||||
'getImageDetailsByID',
|
||||
'getImageByOstree',
|
||||
'createInstallerForImage',
|
||||
'getRepoForImage',
|
||||
'getMetadataForImage',
|
||||
'createKickStartForImage',
|
||||
'checkImageName',
|
||||
'retryCreateImage',
|
||||
'listAllImageSets',
|
||||
'getImageSetsByID',
|
||||
'getImageSetsView',
|
||||
'getImageSetViewByID',
|
||||
'getAllImageSetImagesView',
|
||||
'getImageSetsDevicesByID',
|
||||
'deleteImageSet',
|
||||
'getImageSetImageView',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile:
|
||||
'https://raw.githubusercontent.com/osbuild/image-builder/main/internal/v1/api.yaml',
|
||||
apiFile: '../../src/store/service/emptyImageBuilderApi.ts',
|
||||
schemaFile: '../schema/imageBuilder.yaml',
|
||||
apiFile: '../../src/store/emptyImageBuilderApi.ts',
|
||||
apiImport: 'emptyImageBuilderApi',
|
||||
outputFile: '../../src/store/service/imageBuilderApi.ts',
|
||||
outputFile: '../../src/store/imageBuilderApi.ts',
|
||||
exportName: 'imageBuilderApi',
|
||||
hooks: { queries: true, lazyQueries: true, mutations: true },
|
||||
unionUndefined: true,
|
||||
hooks: true,
|
||||
filterEndpoints: [
|
||||
'cloneCompose',
|
||||
'composeImage',
|
||||
|
|
@ -20,17 +18,6 @@ const config: ConfigFile = {
|
|||
'getPackages',
|
||||
'getOscapProfiles',
|
||||
'getOscapCustomizations',
|
||||
'getOscapCustomizationsForPolicy',
|
||||
'createBlueprint',
|
||||
'updateBlueprint',
|
||||
'composeBlueprint',
|
||||
'getBlueprints',
|
||||
'exportBlueprint',
|
||||
'getBlueprintComposes',
|
||||
'deleteBlueprint',
|
||||
'getBlueprint',
|
||||
'recommendPackage',
|
||||
'fixupBlueprint',
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/provisioning/v1/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyProvisioningApi.ts',
|
||||
schemaFile: '../schema/provisioning.json',
|
||||
apiFile: '../../src/store/emptyProvisioningApi.ts',
|
||||
apiImport: 'emptyProvisioningApi',
|
||||
outputFile: '../../src/store/service/provisioningApi.ts',
|
||||
outputFile: '../../src/store/provisioningApi.ts',
|
||||
exportName: 'provisioningApi',
|
||||
hooks: true,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: ['getSourceList', 'getSourceUploadInfo'],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
import type { ConfigFile } from '@rtk-query/codegen-openapi';
|
||||
|
||||
const config: ConfigFile = {
|
||||
schemaFile: 'https://console.redhat.com/api/rhsm/v2/openapi.json',
|
||||
apiFile: '../../src/store/service/emptyRhsmApi.ts',
|
||||
schemaFile: '../schema/rhsm.json',
|
||||
apiFile: '../../src/store/emptyRhsmApi.ts',
|
||||
apiImport: 'emptyRhsmApi',
|
||||
outputFile: '../../src/store/service/rhsmApi.ts',
|
||||
outputFile: '../../src/store/rhsmApi.ts',
|
||||
exportName: 'rhsmApi',
|
||||
hooks: true,
|
||||
unionUndefined: true,
|
||||
filterEndpoints: [
|
||||
'listActivationKeys',
|
||||
'showActivationKey',
|
||||
'createActivationKeys',
|
||||
],
|
||||
filterEndpoints: ['listActivationKeys', 'showActivationKey'],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
2417
api/schema/contentSources.json
Normal file
2417
api/schema/contentSources.json
Normal file
File diff suppressed because it is too large
Load diff
5722
api/schema/edge.json
Normal file
5722
api/schema/edge.json
Normal file
File diff suppressed because it is too large
Load diff
1181
api/schema/imageBuilder.yaml
Normal file
1181
api/schema/imageBuilder.yaml
Normal file
File diff suppressed because it is too large
Load diff
2044
api/schema/provisioning.json
Normal file
2044
api/schema/provisioning.json
Normal file
File diff suppressed because it is too large
Load diff
1
api/schema/rhsm.json
Normal file
1
api/schema/rhsm.json
Normal file
File diff suppressed because one or more lines are too long
32
babel.config.js
Normal file
32
babel.config.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// copied from https://github.com/RedHatInsights/frontend-starter-app/blob/master/babel.config.js
|
||||
module.exports = {
|
||||
presets: [
|
||||
// Polyfills
|
||||
'@babel/env',
|
||||
'@babel/react',
|
||||
'@babel/typescript',
|
||||
],
|
||||
plugins: [
|
||||
// Put _extends helpers in their own file
|
||||
'@babel/plugin-transform-runtime',
|
||||
// Support for {...props} via Object.assign({}, props)
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
// Devs tend to write `import { someIcon } from '@patternfly/react-icons';`
|
||||
// This transforms the import to be specific which prevents having to parse 2k+ icons
|
||||
// Also prevents potential bundle size blowups with CJS
|
||||
[
|
||||
'transform-imports',
|
||||
{
|
||||
'@patternfly/react-icons': {
|
||||
transform: (importName) =>
|
||||
`@patternfly/react-icons/dist/js/icons/${importName
|
||||
.split(/(?=[A-Z])/)
|
||||
.join('-')
|
||||
.toLowerCase()}`,
|
||||
preventFullImport: true,
|
||||
},
|
||||
},
|
||||
'react-icons',
|
||||
],
|
||||
],
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit b496d0a8c1755608bd256a6960869b14a7689d38
|
||||
|
|
@ -9,7 +9,7 @@ export COMPONENT="image-builder"
|
|||
export IMAGE="quay.io/cloudservices/image-builder-frontend"
|
||||
export APP_ROOT=$(pwd)
|
||||
export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace
|
||||
export NODE_BUILD_VERSION=22
|
||||
export NODE_BUILD_VERSION=16
|
||||
COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master
|
||||
|
||||
set -exv
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
# codebuild buildspec
|
||||
version: 0.2
|
||||
run-as: root
|
||||
|
||||
phases:
|
||||
install:
|
||||
commands:
|
||||
- echo Entered the install phase...
|
||||
- nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2 &
|
||||
- timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
|
||||
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Entered the pre_build phase...
|
||||
|
||||
build:
|
||||
commands:
|
||||
- echo Entered the build phase...
|
||||
|
||||
post_build:
|
||||
commands:
|
||||
- echo Entered the post_build phase...
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- "/root/.cache/ms-playwright"
|
||||
- "/root/.docker"
|
||||
- "/root/.npm"
|
||||
- "/root/.yarn"
|
||||
- "/root/.cache/go-build"
|
||||
- "/root/go"
|
||||
- "/root/.composer/cache"
|
||||
33
ci.sh
33
ci.sh
|
|
@ -1,33 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Workaround needed for Konflux pipeline to pass
|
||||
|
||||
find -name "cockpit" -type d -maxdepth 1
|
||||
find -name "cockpit" -type d -maxdepth 1 | xargs rm -rf -
|
||||
|
||||
setNpmOrYarn
|
||||
install
|
||||
build
|
||||
if [ "$IS_PR" == true ]; then
|
||||
verify
|
||||
else
|
||||
export BETA=false
|
||||
build
|
||||
source build_app_info.sh
|
||||
mv ${DIST_FOLDER} stable
|
||||
export BETA=true
|
||||
# Export sentry specific variables for the webpack plugin. Note that
|
||||
# this only works in jenkins (not konflux). The webpack plugin will
|
||||
# both inject debug ids and upload the sourcemaps, in konflux only
|
||||
# the debug ids are injected. As the debug ids are consistend
|
||||
# across builds, this works.
|
||||
export SENTRY_AUTH_TOKEN
|
||||
build
|
||||
source build_app_info.sh
|
||||
mv ${DIST_FOLDER} preview
|
||||
mkdir -p ${DIST_FOLDER}
|
||||
mv stable ${DIST_FOLDER}/stable
|
||||
mv preview ${DIST_FOLDER}/preview
|
||||
fi
|
||||
|
||||
# End workaround
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# cockpit-image-builder
|
||||
|
||||
The "cockpit-image-builder" provides an on-premise frontend for image building, designed to integrate with [Cockpit](https://cockpit-project.org/) as a plugin. It allows users to create, manage, and compose custom operating system images, with images stored locally.
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
Name: cockpit-image-builder
|
||||
Version: 76
|
||||
Release: 1%{?dist}
|
||||
Summary: Image builder plugin for Cockpit
|
||||
|
||||
License: Apache-2.0
|
||||
URL: http://osbuild.org/
|
||||
Source0: https://github.com/osbuild/image-builder-frontend/releases/download/v%{version}/%{name}-%{version}.tar.gz
|
||||
|
||||
Obsoletes: cockpit-composer < 54
|
||||
Provides: cockpit-composer = %{version}-%{release}
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: gettext
|
||||
BuildRequires: libappstream-glib
|
||||
BuildRequires: make
|
||||
BuildRequires: nodejs
|
||||
|
||||
Requires: cockpit
|
||||
Requires: cockpit-files
|
||||
Requires: osbuild-composer >= 131
|
||||
|
||||
%description
|
||||
The image-builder-frontend generates custom images suitable for
|
||||
deploying systems or uploading to the cloud. It integrates into Cockpit
|
||||
as a frontend for osbuild.
|
||||
|
||||
%prep
|
||||
%setup -q -n %{name}
|
||||
|
||||
%build
|
||||
# Nothing to build
|
||||
|
||||
%install
|
||||
%make_install PREFIX=/usr
|
||||
# drop source maps, they are large and just for debugging
|
||||
find %{buildroot}%{_datadir}/cockpit/ -name '*.map' | xargs --no-run-if-empty rm --verbose
|
||||
|
||||
%check
|
||||
appstream-util validate-relax --nonet %{buildroot}/%{_datadir}/metainfo/*
|
||||
|
||||
%files
|
||||
%doc cockpit/README.md
|
||||
%license LICENSE
|
||||
%{_datadir}/cockpit/cockpit-image-builder
|
||||
%{_datadir}/metainfo/*
|
||||
|
||||
%changelog
|
||||
# the changelog is distribution-specific, therefore there's just one entry
|
||||
# to make rpmlint happy.
|
||||
|
||||
* Mon Jan 13 2025 Image Builder team <osbuilders@redhat.com> - 0-1
|
||||
- The changelog was added to the rpm spec file.
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-us" class="layout-pf pf-m-redhat-font">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Image-Builder</title>
|
||||
|
||||
<!-- js dependencies -->
|
||||
<script type="text/javascript" src="../base1/cockpit.js"></script>
|
||||
<script defer src="main.js"></script>
|
||||
<link href="main.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="ct-page-fill" id="main"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"dashboard": {
|
||||
"index": {
|
||||
"label": "Image Builder",
|
||||
"icon": "pficon-build",
|
||||
"docs": [
|
||||
{
|
||||
"label": "Creating system images",
|
||||
"url": "https://osbuild.org/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"content-security-policy": "default-src 'self' 'unsafe-eval'"
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="addon">
|
||||
<id>org.image-builder.cockpit-image-builder</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<name>Image Builder</name>
|
||||
<summary>
|
||||
Build customized operating system images
|
||||
</summary>
|
||||
<description>
|
||||
<p>
|
||||
Image Builder can generate custom images suitable for deploying
|
||||
systems, or as images ready to upload to the cloud.
|
||||
</p>
|
||||
</description>
|
||||
<extends>org.cockpit_project.cockpit</extends>
|
||||
<url type="homepage">https://github.com/osbuild/image-builder-frontend/</url>
|
||||
<launchable type="cockpit-manifest">cockpit-image-builder</launchable>
|
||||
</component>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
// this allows us to pull in the `cockpit` and
|
||||
// `cockpit/fsinfo` modules from the `pkg/lib`
|
||||
// directory
|
||||
"../pkg/lib/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const webpack = require('webpack'); // Add this line
|
||||
const [mode, devtool] =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? ['production', 'source-map']
|
||||
: ['development', 'inline-source-map'];
|
||||
|
||||
const output = {
|
||||
path: path.resolve('cockpit/public'),
|
||||
filename: 'main.js',
|
||||
sourceMapFilename: '[file].map',
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
new MiniCssExtractPlugin({
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.IS_ON_PREMISE': JSON.stringify(true),
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
entry: './src/AppCockpit.tsx',
|
||||
output,
|
||||
mode,
|
||||
devtool,
|
||||
plugins,
|
||||
devServer: {
|
||||
historyApiFallback: true, // Ensures all routes are served with `index.html`
|
||||
},
|
||||
resolve: {
|
||||
fallback: {
|
||||
path: require.resolve('path-browserify'),
|
||||
},
|
||||
modules: [
|
||||
'node_modules',
|
||||
// this tells webpack to check `node_modules`
|
||||
// and `pkg/lib` for modules. This allows us
|
||||
// to import `cockpit` and `cockpit/fsinfo`
|
||||
path.resolve(__dirname, '../pkg/lib'),
|
||||
],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
include: [
|
||||
path.resolve(__dirname, '../src'),
|
||||
path.resolve(__dirname, '../pkg/lib'),
|
||||
],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
},
|
||||
},
|
||||
resolve: { fullySpecified: false },
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { url: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: { url: false },
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
10
codecov.yml
10
codecov.yml
|
|
@ -1,16 +1,12 @@
|
|||
coverage:
|
||||
status:
|
||||
patch: off
|
||||
patch: no
|
||||
project:
|
||||
default:
|
||||
threshold: 5%
|
||||
codecov:
|
||||
require_ci_to_pass: false
|
||||
require_ci_to_pass: no
|
||||
comment:
|
||||
layout: "reach,diff,flags,files,footer"
|
||||
behavior: default
|
||||
require_changes: false
|
||||
ignore:
|
||||
- "api"
|
||||
- "playwright"
|
||||
- "src/store"
|
||||
require_changes: no
|
||||
|
|
|
|||
112
config/dev.webpack.config.js
Normal file
112
config/dev.webpack.config.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
const { resolve } = require('path');
|
||||
|
||||
const config = require('@redhat-cloud-services/frontend-components-config');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const webpackProxy = {
|
||||
useProxy: true,
|
||||
proxyVerbose: true,
|
||||
env: `${process.env.STAGE ? 'stage' : 'prod'}-${
|
||||
process.env.BETA ? 'beta' : 'stable'
|
||||
}`,
|
||||
appUrl: [
|
||||
'/insights/image-builder',
|
||||
'/beta/insights/image-builder',
|
||||
'/preview/insights/image-builder',
|
||||
],
|
||||
routes: {
|
||||
...(process.env.CONFIG_PORT && {
|
||||
[`${process.env.BETA ? '/beta' : ''}/config`]: {
|
||||
host: `http://localhost:${process.env.CONFIG_PORT}`,
|
||||
},
|
||||
}),
|
||||
...(process.env.LOCAL_API && {
|
||||
...(process.env.LOCAL_API.split(',') || []).reduce((acc, curr) => {
|
||||
const [appName, appConfig] = (curr || '').split(':');
|
||||
const [appPort = 8003, protocol = 'http', host = 'localhost'] =
|
||||
appConfig.split('~');
|
||||
return {
|
||||
...acc,
|
||||
[`/apps/${appName}`]: { host: `${protocol}://${host}:${appPort}` },
|
||||
[`/beta/apps/${appName}`]: {
|
||||
host: `${protocol}://${host}:${appPort}`,
|
||||
},
|
||||
};
|
||||
}, {}),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const { config: webpackConfig, plugins } = config({
|
||||
rootFolder: resolve(__dirname, '../'),
|
||||
debug: true,
|
||||
useFileHash: false,
|
||||
sassPrefix: '.imageBuilder',
|
||||
deployment: process.env.BETA ? 'beta/apps' : 'apps',
|
||||
...(process.env.PROXY ? webpackProxy : {}),
|
||||
});
|
||||
|
||||
plugins.push(
|
||||
require('@redhat-cloud-services/frontend-components-config/federated-modules')(
|
||||
{
|
||||
root: resolve(__dirname, '../'),
|
||||
useFileHash: false,
|
||||
exposes: {
|
||||
'./RootApp': resolve(__dirname, '../src/AppEntry.js'),
|
||||
},
|
||||
shared: [{ 'react-router-dom': { singleton: true } }],
|
||||
exclude: ['react-router-dom'],
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (process.env.MSW) {
|
||||
// Copy mockServiceWorker.js to ./dist/ so it is served with the bundle
|
||||
plugins.push(
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: 'src/mockServiceWorker.js', to: 'mockServiceWorker.js' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/*
|
||||
mockServiceWorker.js will be served from /beta/apps/image-builder, which
|
||||
will become its default scope. Setting the Service-Worker-Allowed header to
|
||||
'/' allows the worker's scope to be expanded to the root route '/'.
|
||||
|
||||
The default webpackConfig for stage does not contain any headers.
|
||||
|
||||
Caution: The default webpackConfig for prod *does* contain headers, so this
|
||||
code will need to be modified if using MSW in prod-beta or prod-stable so that
|
||||
those headers are not overwritten.
|
||||
*/
|
||||
webpackConfig.devServer.headers = { 'Service-Worker-Allowed': '/' };
|
||||
|
||||
/*
|
||||
We would like the client to be able to determine whether or not to start
|
||||
the service worker at run time based on the value of process.env.MSW. We can
|
||||
add that variable to process.env via the DefinesPlugin plugin, but
|
||||
DefinePlugin has already been added by config() to the default webpackConfig.
|
||||
|
||||
Therefore, we find it in the `plugins` array based on its type, then update
|
||||
it to add our new process.env.MSW variable.
|
||||
*/
|
||||
const definePluginIndex = plugins.findIndex(
|
||||
(plugin) => plugin instanceof webpack.DefinePlugin
|
||||
);
|
||||
const definePlugin = plugins[definePluginIndex];
|
||||
|
||||
const newDefinePlugin = new webpack.DefinePlugin({
|
||||
...definePlugin.definitions,
|
||||
'process.env.MSW': true,
|
||||
});
|
||||
|
||||
plugins[definePluginIndex] = newDefinePlugin;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
...webpackConfig,
|
||||
plugins,
|
||||
};
|
||||
39
config/devel.webpack.config.js
Normal file
39
config/devel.webpack.config.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const { resolve } = require('path');
|
||||
|
||||
const config = require('@redhat-cloud-services/frontend-components-config');
|
||||
|
||||
const { config: webpackConfig, plugins } = config({
|
||||
rootFolder: resolve(__dirname, '../'),
|
||||
debug: true,
|
||||
useFileHash: false,
|
||||
sassPrefix: '.imageBuilder',
|
||||
deployment: 'beta/apps',
|
||||
appUrl: '/preview/insights/image-builder',
|
||||
env: 'stage-beta',
|
||||
useProxy: true,
|
||||
useAgent: true,
|
||||
bounceProd: false,
|
||||
proxyVerbose: true,
|
||||
routes: {
|
||||
'/api/image-builder/v1': { host: 'http://localhost:8086' },
|
||||
},
|
||||
});
|
||||
|
||||
plugins.push(
|
||||
require('@redhat-cloud-services/frontend-components-config/federated-modules')(
|
||||
{
|
||||
root: resolve(__dirname, '../'),
|
||||
useFileHash: false,
|
||||
exposes: {
|
||||
'./RootApp': resolve(__dirname, '../src/AppEntry.js'),
|
||||
},
|
||||
shared: [{ 'react-router-dom': { singleton: true } }],
|
||||
exclude: ['react-router-dom'],
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
...webpackConfig,
|
||||
plugins,
|
||||
};
|
||||
25
config/prod.webpack.config.js
Normal file
25
config/prod.webpack.config.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const { resolve } = require('path');
|
||||
|
||||
const config = require('@redhat-cloud-services/frontend-components-config');
|
||||
const { config: webpackConfig, plugins } = config({
|
||||
rootFolder: resolve(__dirname, '../'),
|
||||
sassPrefix: '.imageBuilder',
|
||||
});
|
||||
|
||||
plugins.push(
|
||||
require('@redhat-cloud-services/frontend-components-config/federated-modules')(
|
||||
{
|
||||
root: resolve(__dirname, '../'),
|
||||
exposes: {
|
||||
'./RootApp': resolve(__dirname, '../src/AppEntry.js'),
|
||||
},
|
||||
shared: [{ 'react-router-dom': { singleton: true } }],
|
||||
exclude: ['react-router-dom'],
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
...webpackConfig,
|
||||
plugins,
|
||||
};
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"/apps/image-builder*": { "url": "http://127.0.0.1:8003" }
|
||||
}
|
||||
12
distribution/Dockerfile
Normal file
12
distribution/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm ci
|
||||
|
||||
EXPOSE 8002
|
||||
EXPOSE 1337
|
||||
|
||||
CMD [ "npm", "run", "devel" ]
|
||||
174
eslint.config.js
174
eslint.config.js
|
|
@ -1,174 +0,0 @@
|
|||
const js = require('@eslint/js');
|
||||
const tseslint = require('typescript-eslint');
|
||||
const pluginReact = require('eslint-plugin-react');
|
||||
const pluginReactHooks = require('eslint-plugin-react-hooks');
|
||||
const pluginReactRedux = require('eslint-plugin-react-redux');
|
||||
const pluginImport = require('eslint-plugin-import');
|
||||
const fecConfig = require('@redhat-cloud-services/eslint-config-redhat-cloud-services');
|
||||
const pluginJsxA11y = require('eslint-plugin-jsx-a11y');
|
||||
const disableAutofix = require('eslint-plugin-disable-autofix');
|
||||
const pluginPrettier = require('eslint-plugin-prettier');
|
||||
const jestDom = require('eslint-plugin-jest-dom');
|
||||
const pluginTestingLibrary = require('eslint-plugin-testing-library');
|
||||
const pluginPlaywright = require('eslint-plugin-playwright');
|
||||
const { defineConfig } = require('eslint/config');
|
||||
const globals = require('globals');
|
||||
|
||||
module.exports = defineConfig([
|
||||
{ // Ignore programatically generated files
|
||||
ignores: [
|
||||
'**/mockServiceWorker.js',
|
||||
'**/imageBuilderApi.ts',
|
||||
'**/contentSourcesApi.ts',
|
||||
'**/rhsmApi.ts',
|
||||
'**/provisioningApi.ts',
|
||||
'**/complianceApi.ts',
|
||||
'**/composerCloudApi.ts'
|
||||
]
|
||||
},
|
||||
|
||||
{ // Base config for js/ts files
|
||||
files: ['**/*.{js,ts,jsx,tsx}'],
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json'
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
// node
|
||||
'JSX': 'readonly',
|
||||
'process': 'readonly',
|
||||
'__dirname': 'readonly',
|
||||
'require': 'readonly',
|
||||
// vitest
|
||||
'describe': 'readonly',
|
||||
'it': 'readonly',
|
||||
'test': 'readonly',
|
||||
'expect': 'readonly',
|
||||
'vi': 'readonly',
|
||||
'beforeAll': 'readonly',
|
||||
'beforeEach': 'readonly',
|
||||
'afterAll': 'readonly',
|
||||
'afterEach': 'readonly'
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
js,
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
react: pluginReact,
|
||||
'react-hooks': pluginReactHooks,
|
||||
'react-redux': pluginReactRedux,
|
||||
import: pluginImport,
|
||||
jsxA11y: pluginJsxA11y,
|
||||
'disable-autofix': disableAutofix,
|
||||
prettier: pluginPrettier,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...tseslint.configs.recommended.rules,
|
||||
...pluginReact.configs.flat.recommended.rules,
|
||||
...pluginReactHooks.configs.recommended.rules,
|
||||
...pluginReactRedux.configs.recommended.rules,
|
||||
...fecConfig.rules,
|
||||
'import/order': ['error', {
|
||||
groups: ['builtin', 'external', 'internal', 'sibling', 'parent', 'index'],
|
||||
alphabetize: {
|
||||
order: 'asc',
|
||||
caseInsensitive: true
|
||||
},
|
||||
'newlines-between': 'always',
|
||||
pathGroups: [ // ensures the import of React is always on top
|
||||
{
|
||||
pattern: 'react',
|
||||
group: 'builtin',
|
||||
position: 'before'
|
||||
}
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ['react']
|
||||
}],
|
||||
'sort-imports': ['error', {
|
||||
ignoreCase: true,
|
||||
ignoreDeclarationSort: true,
|
||||
ignoreMemberSort: false,
|
||||
}],
|
||||
'no-duplicate-imports': 'error',
|
||||
'prefer-const': ['error', {
|
||||
destructuring: 'any',
|
||||
}],
|
||||
'no-console': 'error',
|
||||
'eqeqeq': 'error',
|
||||
'array-callback-return': 'warn',
|
||||
'@typescript-eslint/ban-ts-comment': ['error', {
|
||||
'ts-expect-error': 'allow-with-description',
|
||||
'ts-ignore': 'allow-with-description',
|
||||
'ts-nocheck': true,
|
||||
'ts-check': true,
|
||||
minimumDescriptionLength: 5,
|
||||
}],
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'error',
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'disable-autofix/@typescript-eslint/no-unnecessary-condition': 'warn',
|
||||
'no-unused-vars': 'off', // disable js rule in favor of @typescript-eslint's rule
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'jsx-a11y/no-autofocus': 'off',
|
||||
'prettier/prettier': ['error', {
|
||||
semi: true,
|
||||
tabWidth: 2,
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
bracketSpacing: true,
|
||||
tsxSingleQuote: true,
|
||||
tsSingleQuote: true,
|
||||
printWidth: 80,
|
||||
trailingComma: 'all',
|
||||
}],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Automatically detect React version
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{ // Override for test files
|
||||
files: ['src/test/**/*.{ts,tsx}'],
|
||||
plugins: {
|
||||
'jest-dom': jestDom,
|
||||
'testing-library': pluginTestingLibrary,
|
||||
},
|
||||
rules: {
|
||||
...jestDom.configs.recommended.rules,
|
||||
...pluginTestingLibrary.configs.react.rules,
|
||||
'react/display-name': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'testing-library/no-debugging-utils': 'error'
|
||||
},
|
||||
},
|
||||
|
||||
{ // Override for Playwright tests
|
||||
files: ['playwright/**/*.ts'],
|
||||
plugins: {
|
||||
playwright: pluginPlaywright,
|
||||
},
|
||||
rules: {
|
||||
...pluginPlaywright.configs.recommended.rules,
|
||||
'playwright/no-conditional-in-test': 'off',
|
||||
'playwright/no-conditional-expect': 'off',
|
||||
'playwright/no-skipped-test': [
|
||||
'error',
|
||||
{
|
||||
'allowConditional': true
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
]);
|
||||
150
fec.config.js
150
fec.config.js
|
|
@ -1,150 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require('path');
|
||||
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const plugins = [];
|
||||
|
||||
function add_define(key, value) {
|
||||
const definePluginIndex = plugins.findIndex(
|
||||
(plugin) => plugin instanceof webpack.DefinePlugin
|
||||
);
|
||||
if (definePluginIndex !== -1) {
|
||||
const definePlugin = plugins[definePluginIndex];
|
||||
|
||||
const newDefinePlugin = new webpack.DefinePlugin({
|
||||
...definePlugin.definitions,
|
||||
[key]: JSON.stringify(value),
|
||||
});
|
||||
|
||||
plugins[definePluginIndex] = newDefinePlugin;
|
||||
} else {
|
||||
plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
[key]: JSON.stringify(value),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.MSW) {
|
||||
// Copy mockServiceWorker.js to ./dist/ so it is served with the bundle
|
||||
plugins.push(
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{ from: 'src/mockServiceWorker.js', to: 'mockServiceWorker.js' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/*
|
||||
We would like the client to be able to determine whether or not to start
|
||||
the service worker at run time based on the value of process.env.MSW. We can
|
||||
add that variable to process.env via the DefinesPlugin plugin, but
|
||||
DefinePlugin has already been added by config() to the default webpackConfig.
|
||||
|
||||
Therefore, we find it in the `plugins` array based on its type, then update
|
||||
it to add our new process.env.MSW variable.
|
||||
*/
|
||||
add_define('process.env.MSW', process.env.MSW);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV) {
|
||||
add_define('process.env.NODE_ENV', process.env.NODE_ENV);
|
||||
}
|
||||
|
||||
if (process.env.ENABLE_SENTRY) {
|
||||
plugins.push(
|
||||
sentryWebpackPlugin({
|
||||
...(process.env.SENTRY_AUTH_TOKEN && {
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
}),
|
||||
org: 'red-hat-it',
|
||||
project: 'image-builder-rhel',
|
||||
moduleMetadata: ({ release }) => ({
|
||||
dsn: 'https://f4b4288bbb7cf6c0b2ac1a2b90a076bf@o490301.ingest.us.sentry.io/4508297557901312',
|
||||
org: 'red-hat-it',
|
||||
project: 'image-builder-rhel',
|
||||
release,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sassPrefix: '.imageBuilder',
|
||||
debug: true,
|
||||
useFileHash: true,
|
||||
/*
|
||||
mockServiceWorker.js will be served from /beta/apps/image-builder, which
|
||||
will become its default scope. Setting the Service-Worker-Allowed header to
|
||||
'/' allows the worker's scope to be expanded to the root route '/'.
|
||||
|
||||
The default webpackConfig for stage does not contain any headers.
|
||||
|
||||
Caution: The default webpackConfig for prod *does* contain headers, so this
|
||||
code will need to be modified if using MSW in prod-beta or prod-stable so that
|
||||
those headers are not overwritten.
|
||||
*/
|
||||
devServer: process.env.MSW && {
|
||||
headers: { 'Service-Worker-Allowed': '/' },
|
||||
},
|
||||
devtool: 'hidden-source-map',
|
||||
appUrl: '/insights/image-builder',
|
||||
useProxy: true,
|
||||
useAgent: true,
|
||||
bounceProd: false,
|
||||
proxyVerbose: true,
|
||||
resolve: {
|
||||
alias: {
|
||||
// we don't wan't these packages bundled with
|
||||
// the service frontend, so we can set the aliases
|
||||
// to false
|
||||
cockpit: false,
|
||||
'cockpit/fsinfo': false,
|
||||
'os-release': false,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
// running `make` on cockpit plugin creates './pkg'
|
||||
// directory, the generated files do not pass
|
||||
// `npm run build` outputing failures
|
||||
// this ensures the directory is exluded during build time
|
||||
exclude: ',/pkg',
|
||||
},
|
||||
],
|
||||
},
|
||||
routes: {
|
||||
...(process.env.CONFIG_PORT && {
|
||||
[`${process.env.BETA ? '/beta' : ''}/config`]: {
|
||||
host: `http://localhost:${process.env.CONFIG_PORT}`,
|
||||
},
|
||||
}),
|
||||
...(process.env.LOCAL_API && {
|
||||
...(process.env.LOCAL_API.split(',') || []).reduce((acc, curr) => {
|
||||
const [appName, appConfig] = (curr || '').split(':');
|
||||
const [appPort = 8003, protocol = 'http', host = 'localhost'] =
|
||||
appConfig.split('~');
|
||||
return {
|
||||
...acc,
|
||||
[`/apps/${appName}`]: { host: `${protocol}://${host}:${appPort}` },
|
||||
[`/beta/apps/${appName}`]: {
|
||||
host: `${protocol}://${host}:${appPort}`,
|
||||
},
|
||||
};
|
||||
}, {}),
|
||||
}),
|
||||
},
|
||||
plugins: plugins,
|
||||
moduleFederation: {
|
||||
exposes: {
|
||||
'./RootApp': path.resolve(__dirname, './src/AppEntry.tsx'),
|
||||
},
|
||||
shared: [{ 'react-router-dom': { singleton: true, version: '*' } }],
|
||||
exclude: ['react-router-dom'],
|
||||
},
|
||||
};
|
||||
11
jest.setup.js
Normal file
11
jest.setup.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import 'whatwg-fetch';
|
||||
import { server } from './src/test/mocks/server';
|
||||
|
||||
jest.mock('@unleash/proxy-client-react', () => ({
|
||||
useUnleashContext: () => jest.fn(),
|
||||
useFlag: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
33857
package-lock.json
generated
33857
package-lock.json
generated
File diff suppressed because it is too large
Load diff
208
package.json
208
package.json
|
|
@ -7,121 +7,121 @@
|
|||
"npm": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ltd/j-toml": "1.38.0",
|
||||
"@patternfly/patternfly": "6.3.1",
|
||||
"@patternfly/react-code-editor": "6.3.1",
|
||||
"@patternfly/react-core": "6.3.1",
|
||||
"@patternfly/react-table": "6.3.1",
|
||||
"@redhat-cloud-services/frontend-components": "7.0.3",
|
||||
"@redhat-cloud-services/frontend-components-notifications": "6.1.5",
|
||||
"@redhat-cloud-services/frontend-components-utilities": "7.0.3",
|
||||
"@redhat-cloud-services/types": "3.0.1",
|
||||
"@reduxjs/toolkit": "2.8.2",
|
||||
"@scalprum/react-core": "0.9.5",
|
||||
"@sentry/webpack-plugin": "4.1.1",
|
||||
"@unleash/proxy-client-react": "5.0.1",
|
||||
"classnames": "2.5.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"@data-driven-forms/pf4-component-mapper": "3.20.13",
|
||||
"@data-driven-forms/react-form-renderer": "3.21.7",
|
||||
"@patternfly/patternfly": "4.224.2",
|
||||
"@patternfly/react-core": "4.276.8",
|
||||
"@patternfly/react-table": "4.113.3",
|
||||
"@redhat-cloud-services/frontend-components": "3.11.2",
|
||||
"@redhat-cloud-services/frontend-components-notifications": "3.2.14",
|
||||
"@redhat-cloud-services/frontend-components-utilities": "3.7.4",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@scalprum/react-core": "^0.5.1",
|
||||
"@unleash/proxy-client-react": "^3.6.0",
|
||||
"classnames": "2.3.2",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router-dom": "6.27.0",
|
||||
"redux": "5.0.1",
|
||||
"redux-promise-middleware": "6.2.0"
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-redux": "8.1.2",
|
||||
"react-router-dom": "6.16.0",
|
||||
"redux": "4.2.1",
|
||||
"redux-promise-middleware": "6.1.3"
|
||||
},
|
||||
"jest": {
|
||||
"coverageDirectory": "./coverage/",
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.js",
|
||||
"!src/**/stories/*",
|
||||
"!src/entry-dev.js"
|
||||
],
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"<rootDir>/src/"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(css|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(@scalprum|@openshift|lodash-es|uuid)/)"
|
||||
],
|
||||
"setupFiles": [
|
||||
"jest-canvas-mock"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"./src/test/jest.setup.js"
|
||||
],
|
||||
"testTimeout": 10000
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@currents/playwright": "1.15.3",
|
||||
"@eslint/js": "9.32.0",
|
||||
"@patternfly/react-icons": "6.3.1",
|
||||
"@playwright/test": "1.51.1",
|
||||
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "3.0.0",
|
||||
"@redhat-cloud-services/frontend-components-config": "6.3.8",
|
||||
"@redhat-cloud-services/tsc-transform-imports": "1.0.25",
|
||||
"@rtk-query/codegen-openapi": "2.0.0",
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "6.6.4",
|
||||
"@testing-library/react": "16.3.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/node": "24.3.0",
|
||||
"@types/react": "18.3.12",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react-redux": "7.1.34",
|
||||
"@types/uuid": "10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@vitejs/plugin-react": "4.7.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"babel-loader": "10.0.0",
|
||||
"chart.js": "4.5.0",
|
||||
"chartjs-adapter-moment": "1.0.1",
|
||||
"chartjs-plugin-annotation": "3.1.0",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"eslint": "9.33.0",
|
||||
"eslint-plugin-disable-autofix": "5.0.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jest-dom": "5.5.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-playwright": "2.2.2",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"eslint-plugin-react-redux": "4.2.2",
|
||||
"eslint-plugin-testing-library": "7.6.6",
|
||||
"@babel/core": "7.22.10",
|
||||
"@babel/eslint-parser": "^7.22.9",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|
||||
"@babel/plugin-transform-runtime": "7.22.10",
|
||||
"@babel/preset-env": "7.22.9",
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@redhat-cloud-services/eslint-config-redhat-cloud-services": "2.0.3",
|
||||
"@redhat-cloud-services/frontend-components-config": "5.0.5",
|
||||
"@rtk-query/codegen-openapi": "^1.0.0",
|
||||
"@testing-library/dom": "9.3.1",
|
||||
"@testing-library/jest-dom": "6.1.3",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.21",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"babel-jest": "29.6.2",
|
||||
"babel-plugin-dual-import": "1.2.1",
|
||||
"babel-plugin-transform-imports": "2.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "6.8.1",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-jest-dom": "5.1.0",
|
||||
"eslint-plugin-react": "7.33.0",
|
||||
"eslint-plugin-testing-library": "5.11.1",
|
||||
"git-revision-webpack-plugin": "5.0.0",
|
||||
"globals": "16.3.0",
|
||||
"history": "5.3.0",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"jsdom": "26.1.0",
|
||||
"madge": "8.0.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"moment": "2.30.1",
|
||||
"msw": "2.10.5",
|
||||
"jest": "^29.6.2",
|
||||
"jest-canvas-mock": "2.5.2",
|
||||
"jest-environment-jsdom": "29.6.3",
|
||||
"jest-fail-on-console": "^3.1.1",
|
||||
"msw": "^1.2.3",
|
||||
"npm-run-all": "4.1.5",
|
||||
"path-browserify": "1.0.1",
|
||||
"postcss-scss": "4.0.9",
|
||||
"react-chartjs-2": "5.3.0",
|
||||
"redux-mock-store": "1.5.5",
|
||||
"sass": "1.90.0",
|
||||
"sass-loader": "16.0.5",
|
||||
"stylelint": "16.23.1",
|
||||
"stylelint-config-recommended-scss": "16.0.0",
|
||||
"ts-node": "10.9.2",
|
||||
"ts-patch": "3.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.40.0",
|
||||
"uuid": "11.1.0",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-canvas-mock": "0.3.3",
|
||||
"webpack-bundle-analyzer": "4.10.2",
|
||||
"whatwg-fetch": "3.6.20"
|
||||
"prop-types": "15.8.1",
|
||||
"redux-mock-store": "1.5.4",
|
||||
"sass": "1.66.1",
|
||||
"sass-loader": "13.3.2",
|
||||
"stylelint": "15.10.3",
|
||||
"stylelint-config-recommended-scss": "12.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "5.1.6",
|
||||
"uuid": "9.0.0",
|
||||
"webpack-bundle-analyzer": "4.9.0",
|
||||
"whatwg-fetch": "^3.6.17"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:js": "eslint src playwright",
|
||||
"lint:js:fix": "eslint src playwright --fix",
|
||||
"start": "fec dev",
|
||||
"start:stage": "fec dev --clouddotEnv=stage",
|
||||
"start:prod": "fec dev --clouddotEnv=prod",
|
||||
"start:msw:stage": "NODE_ENV=development MSW=TRUE fec dev --clouddotEnv=stage",
|
||||
"start:federated": "fec static",
|
||||
"patch:hosts": "fec patch-etc-hosts",
|
||||
"test": "TZ=UTC vitest run",
|
||||
"test:watch": "TZ=UTC vitest",
|
||||
"test:coverage": "TZ=UTC vitest run --coverage",
|
||||
"test:cockpit": "src/test/cockpit-tests.sh",
|
||||
"build": "fec build",
|
||||
"build:cockpit": "webpack --config cockpit/webpack.config.ts",
|
||||
"api": "bash api/codegen.sh",
|
||||
"verify": "npm-run-all build lint test",
|
||||
"postinstall": "ts-patch install",
|
||||
"circular": "madge --circular ./src --extensions js,ts,tsx",
|
||||
"circular:graph": "madge --circular ./src --extensions js,ts,tsx -i deps.png"
|
||||
"lint:js": "eslint config src",
|
||||
"lint:js:fix": "eslint config src --fix",
|
||||
"lint:sass": "stylelint 'src/**/*.scss' --config .stylelintrc.json",
|
||||
"devel": "webpack serve --config config/devel.webpack.config.js",
|
||||
"prod-beta": "BETA=true PROXY=true webpack serve --config config/dev.webpack.config.js",
|
||||
"prod-stable": "PROXY=true webpack serve --config config/dev.webpack.config.js",
|
||||
"stage-stable": "STAGE=true npm run prod-stable",
|
||||
"stage-beta": "STAGE=true npm run prod-beta",
|
||||
"stage-beta:msw": "MSW=TRUE npm run stage-beta",
|
||||
"test": "TZ=UTC jest --verbose --no-cache",
|
||||
"test:single": "jest --verbose -w 1",
|
||||
"build": "webpack --config config/prod.webpack.config.js",
|
||||
"api": "bash api.sh",
|
||||
"verify": "npm-run-all build lint test"
|
||||
},
|
||||
"insights": {
|
||||
"appname": "image-builder"
|
||||
|
|
|
|||
66
packit.yaml
66
packit.yaml
|
|
@ -1,66 +0,0 @@
|
|||
upstream_project_url: https://github.com/osbuild/image-builder-frontend
|
||||
specfile_path: cockpit/cockpit-image-builder.spec
|
||||
upstream_package_name: cockpit-image-builder
|
||||
downstream_package_name: cockpit-image-builder
|
||||
# use the nicely formatted release description from our upstream release, instead of git shortlog
|
||||
copy_upstream_release_description: true
|
||||
upstream_tag_template: v{version}
|
||||
|
||||
actions:
|
||||
create-archive:
|
||||
- npm ci
|
||||
- make dist
|
||||
|
||||
srpm_build_deps:
|
||||
- make
|
||||
- npm
|
||||
|
||||
jobs:
|
||||
- job: tests
|
||||
identifier: self
|
||||
trigger: pull_request
|
||||
tmt_plan: /plans/all/main
|
||||
targets:
|
||||
- centos-stream-10
|
||||
- fedora-41
|
||||
- fedora-42
|
||||
|
||||
- job: copr_build
|
||||
trigger: pull_request
|
||||
targets: &build_targets
|
||||
- centos-stream-9
|
||||
- centos-stream-9-aarch64
|
||||
- centos-stream-10
|
||||
- centos-stream-10-aarch64
|
||||
- fedora-all
|
||||
|
||||
- job: copr_build
|
||||
trigger: commit
|
||||
branch: "^main$"
|
||||
owner: "@osbuild"
|
||||
project: "cockpit-image-builder-main"
|
||||
preserve_project: True
|
||||
targets: *build_targets
|
||||
|
||||
- job: copr_build
|
||||
trigger: release
|
||||
owner: "@osbuild"
|
||||
project: "cockpit-image-builder"
|
||||
preserve_project: True
|
||||
targets: *build_targets
|
||||
actions:
|
||||
create-archive:
|
||||
- npm ci
|
||||
- make dist
|
||||
|
||||
- job: propose_downstream
|
||||
trigger: release
|
||||
dist_git_branches:
|
||||
- fedora-42
|
||||
- fedora-rawhide
|
||||
|
||||
- job: koji_build
|
||||
trigger: commit
|
||||
dist_git_branches:
|
||||
- fedora-42
|
||||
- fedora-rawhide
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
summary: cockpit-image-builder playwright tests
|
||||
prepare:
|
||||
how: install
|
||||
package:
|
||||
- cockpit-image-builder
|
||||
discover:
|
||||
how: fmf
|
||||
execute:
|
||||
how: tmt
|
||||
|
||||
/main:
|
||||
summary: playwright tests
|
||||
discover+:
|
||||
test: /schutzbot/playwright
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import {
|
||||
defineConfig,
|
||||
devices,
|
||||
type ReporterDescription,
|
||||
} from '@playwright/test';
|
||||
import 'dotenv/config';
|
||||
|
||||
const reporters: ReporterDescription[] = [['html'], ['list']];
|
||||
|
||||
if (process.env.CURRENTS_PROJECT_ID && process.env.CURRENTS_RECORD_KEY) {
|
||||
reporters.push(['@currents/playwright']);
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
testDir: 'playwright',
|
||||
fullyParallel: true,
|
||||
workers: 4,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
reporter: reporters,
|
||||
globalTimeout: 29.5 * 60 * 1000, // 29.5m, Set because of codebuild, we want PW to timeout before CB to get the results.
|
||||
timeout: 3 * 60 * 1000, // 3m
|
||||
expect: { timeout: 50_000 }, // 50s
|
||||
use: {
|
||||
actionTimeout: 30_000, // 30s
|
||||
navigationTimeout: 30_000, // 30s
|
||||
headless: true,
|
||||
baseURL: process.env.BASE_URL
|
||||
? process.env.BASE_URL
|
||||
: 'http://127.0.0.1:9090',
|
||||
video: 'retain-on-failure',
|
||||
trace: 'on',
|
||||
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
projects: [
|
||||
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
storageState: '.auth/user.json',
|
||||
},
|
||||
dependencies: ['setup'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
const validCallbackUrl =
|
||||
'https://controller.url/api/controller/v2/job_templates/9/callback/';
|
||||
const validHttpCallbackUrl =
|
||||
'http://controller.url/api/controller/v2/job_templates/9/callback/';
|
||||
const validHostConfigKey = 'hostconfigkey';
|
||||
const validCertificate = `-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOEzx5ezZ9EIMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAklOMQswCQYDVQQIDAJLUjEMMAoGA1UEBwwDS1JHMRAwDgYDVQQKDAdUZXN0
|
||||
IENBMB4XDTI1MDUxNTEyMDAwMFoXDTI2MDUxNTEyMDAwMFowRTELMAkGA1UEBhMC
|
||||
SU4xCzAJBgNVBAgMAktSMQwwCgYDVQQHDANSR0sxEDAOBgNVBAoMB1Rlc3QgQ0Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+R4gfN5pyJQo5qBTTtN+7
|
||||
eE9CSXZJ8SVVaE3U54IgqQoqsSoBY5QtExy7v5C6l6mW4E6dzK/JecmvTTO/BvlG
|
||||
A5k2hxB6bOQxtxYwfgElH+RFWN9P4xxhtEiQgHoG1rDfnXuDJk1U3YEkCQELUebz
|
||||
fF3EIDU1yR0Sz2bA+Sl2VXe8og1MEZfytq8VZUVltxtn2PfW7zI5gOllBR2sKeUc
|
||||
K6h8HXN7qMgfEvsLIXxTw7fU/zA3ibcxfRCl3m6QhF8hwRh6F9Wtz2s8hCzGegV5
|
||||
z0M39nY7X8C3GZQ4Ly8v8DdY+FbEix7K3SSBRbWtdPfAHRFlX9Er2Wf8DAr7O2hH
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBTXXz2eIDgK+BhzDUAGzptn0OMcpDAfBgNVHSME
|
||||
GDAWgBTXXz2eIDgK+BhzDUAGzptn0OMcpDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQAoUgY4jsuBMB3el9cc7JS2rcOhhJzn47Hj2UANfJq52g5lbjo7
|
||||
XDc7Wb3VDcV+1LzjdzayT1qO1WzHb6FDPW9L9f6h4s8lj6MvJ+xhOWgD11srdIt3
|
||||
vbQaQW4zDfeVRcKXzqbcUX8BLXAdzJPqVwZ+Z4EDjYrJ7lF9k+IqfZm0MsYX7el9
|
||||
kvdRHbLuF4Q0sZ05CXMFkhM0Ulhu4MZ+1FcsQa7nWfZzTmbjHOuWJPB4z5WwrB7z
|
||||
U8YYvWJ3qxToWGbATqJxkRKGGqLrNrmwcfzgPqkpuCRYi0Kky6gJ1RvL+DRopY9x
|
||||
uD+ckf3oH2wYAB6RpPRMkfVxe7lGMvq/yEZ6
|
||||
-----END CERTIFICATE-----`;
|
||||
const invalidCertificate = `-----BEGIN CERTIFICATE-----
|
||||
ThisIs*Not+Valid/Base64==
|
||||
-----END CERTIFICATE-----`;
|
||||
|
||||
test('Create a blueprint with AAP registration customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Skip entirely in Cockpit/on-premise where AAP customization is unavailable
|
||||
test.skip(!isHosted(), 'AAP customization is not available in the plugin');
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the AAP step with valid configuration', async () => {
|
||||
await frame
|
||||
.getByRole('button', { name: 'Ansible Automation Platform' })
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validCallbackUrl);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'host config key' })
|
||||
.fill(validHostConfigKey);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(validCertificate);
|
||||
await expect(frame.getByRole('button', { name: 'Next' })).toBeEnabled();
|
||||
});
|
||||
|
||||
await test.step('Test TLS confirmation checkbox for HTTPS URLs', async () => {
|
||||
// TLS confirmation checkbox should appear for HTTPS URLs
|
||||
await expect(
|
||||
frame.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
// Check TLS confirmation and verify CA input is hidden
|
||||
await frame
|
||||
.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
})
|
||||
.check();
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeHidden();
|
||||
|
||||
await frame
|
||||
.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
})
|
||||
.uncheck();
|
||||
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test certificate validation', async () => {
|
||||
await frame.getByRole('textbox', { name: 'File upload' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(invalidCertificate);
|
||||
await expect(frame.getByText(/Certificate.*is not valid/)).toBeVisible();
|
||||
|
||||
await frame.getByRole('textbox', { name: 'File upload' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'File upload' })
|
||||
.fill(validCertificate);
|
||||
|
||||
await expect(frame.getByText('Certificate was uploaded')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Test HTTP URL behavior', async () => {
|
||||
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validHttpCallbackUrl);
|
||||
|
||||
// TLS confirmation checkbox should NOT appear for HTTP URLs
|
||||
await expect(
|
||||
frame.getByRole('checkbox', {
|
||||
name: 'Insecure',
|
||||
}),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toBeVisible();
|
||||
|
||||
await frame.getByRole('textbox', { name: 'ansible callback url' }).clear();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'ansible callback url' })
|
||||
.fill(validCallbackUrl);
|
||||
});
|
||||
|
||||
await test.step('Complete AAP configuration and proceed to review', async () => {
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP and verify AAP configuration persists', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Ansible Automation Platform step').click();
|
||||
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'ansible callback url' }),
|
||||
).toHaveValue(validCallbackUrl);
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'host config key' }),
|
||||
).toHaveValue(validHostConfigKey);
|
||||
await expect(
|
||||
frame.getByRole('textbox', { name: 'File upload' }),
|
||||
).toHaveValue(validCertificate);
|
||||
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page
|
||||
.getByRole('button', { name: 'Ansible Automation Platform' })
|
||||
.click();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'ansible callback url' }),
|
||||
).toHaveValue(validCallbackUrl);
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'host config key' }),
|
||||
).toBeEmpty();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'File upload' }),
|
||||
).toHaveValue(validCertificate);
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { FILE_SYSTEM_CUSTOMIZATION_URL } from '../../src/constants';
|
||||
import { test } from '../fixtures/cleanup';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Filesystem customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Login, navigate to IB and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Check URLs for documentation', async () => {
|
||||
await frame
|
||||
.getByRole('button', { name: 'File system configuration' })
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('radio', { name: 'Use automatic partitioning' })
|
||||
.click();
|
||||
const [newPageAutomatic] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
frame
|
||||
.getByRole('link', {
|
||||
name: 'Customizing file systems during the image creation',
|
||||
})
|
||||
.click(),
|
||||
]);
|
||||
await newPageAutomatic.waitForLoadState();
|
||||
const finalUrlAutomatic = newPageAutomatic.url();
|
||||
expect(finalUrlAutomatic).toContain(FILE_SYSTEM_CUSTOMIZATION_URL);
|
||||
await newPageAutomatic.close();
|
||||
|
||||
await frame
|
||||
.getByRole('radio', { name: 'Manually configure partitions' })
|
||||
.click();
|
||||
const [newPageManual] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
frame
|
||||
.getByRole('link', {
|
||||
name: 'Read more about manual configuration here',
|
||||
})
|
||||
.click(),
|
||||
]);
|
||||
await newPageManual.waitForLoadState();
|
||||
const finalUrlManual = newPageManual.url();
|
||||
expect(finalUrlManual).toContain(FILE_SYSTEM_CUSTOMIZATION_URL);
|
||||
await newPageManual.close();
|
||||
});
|
||||
|
||||
await test.step('Fill manually selected partitions', async () => {
|
||||
await expect(frame.getByRole('button', { name: '/' })).toBeDisabled();
|
||||
const closeRootButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button / xfs 10 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
await expect(closeRootButton).toBeDisabled();
|
||||
|
||||
await frame.getByRole('button', { name: 'Add partition' }).click();
|
||||
await frame.getByRole('button', { name: '/home' }).click();
|
||||
await frame.getByRole('option', { name: '/tmp' }).click();
|
||||
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'mountpoint suffix' })
|
||||
.fill('/usb');
|
||||
await frame
|
||||
.getByRole('gridcell', { name: '1', exact: true })
|
||||
.getByPlaceholder('File system')
|
||||
.fill('1000');
|
||||
await frame.getByRole('button', { name: 'GiB' }).nth(1).click();
|
||||
await frame.getByRole('option', { name: 'KiB' }).click();
|
||||
|
||||
const closeTmpButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button /tmp /usb xfs 1000 KiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
|
||||
await expect(closeTmpButton).toBeEnabled();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit File system configuration step').click();
|
||||
|
||||
const closeRootButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button / xfs 10 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
await expect(closeRootButton).toBeDisabled();
|
||||
|
||||
const closeTmpButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button /tmp /usb xfs 1000 KiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
await expect(closeTmpButton).toBeEnabled();
|
||||
|
||||
const usbTextbox = frame.getByRole('textbox', {
|
||||
name: 'mountpoint suffix',
|
||||
});
|
||||
await expect(usbTextbox).toHaveValue('/usb');
|
||||
|
||||
await frame
|
||||
.getByRole('gridcell', { name: '1000', exact: true })
|
||||
.getByPlaceholder('File system')
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('gridcell', { name: '1000', exact: true })
|
||||
.getByPlaceholder('File system')
|
||||
.fill('1024');
|
||||
|
||||
await frame.getByRole('button', { name: '/tmp' }).click();
|
||||
await frame.getByRole('option', { name: '/usr' }).click();
|
||||
await expect(
|
||||
frame.getByText(
|
||||
'Sub-directories for the /usr mount point are no longer supported',
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await frame.getByRole('button', { name: '/usr' }).click();
|
||||
await frame.getByRole('option', { name: '/srv' }).click();
|
||||
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'mountpoint suffix' })
|
||||
.fill('/data');
|
||||
|
||||
await frame.getByRole('button', { name: 'KiB' }).click();
|
||||
await frame.getByRole('option', { name: 'MiB' }).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await frame
|
||||
.getByRole('button', { name: 'File system configuration' })
|
||||
.click();
|
||||
|
||||
const closeRootButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button / xfs 10 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
await expect(closeRootButton).toBeDisabled();
|
||||
|
||||
const closeTmpButton = frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button /srv /data xfs 1 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3);
|
||||
await expect(closeTmpButton).toBeEnabled();
|
||||
|
||||
const dataTextbox = frame.getByRole('textbox', {
|
||||
name: 'mountpoint suffix',
|
||||
});
|
||||
await expect(dataTextbox).toHaveValue('/data');
|
||||
|
||||
const size = frame
|
||||
.getByRole('gridcell', { name: '1', exact: true })
|
||||
.getByPlaceholder('File system');
|
||||
await expect(size).toHaveValue('1');
|
||||
|
||||
const unitButton = frame.getByRole('button', { name: 'GiB' }).nth(1);
|
||||
await expect(unitButton).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Firewall customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and correctly fill the ports in Firewall step', async () => {
|
||||
await frame.getByRole('button', { name: 'Firewall' }).click();
|
||||
await frame.getByPlaceholder('Add ports').fill('80:tcp');
|
||||
await frame.getByRole('button', { name: 'Add ports' }).click();
|
||||
await expect(frame.getByText('80:tcp')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and correctly fill the disabled services in Firewall step', async () => {
|
||||
await frame
|
||||
.getByPlaceholder('Add disabled service')
|
||||
.fill('disabled_service');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await expect(frame.getByText('disabled_service')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and correctly fill the enabled services in Firewall step', async () => {
|
||||
await frame.getByPlaceholder('Add enabled service').fill('enabled_service');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
await expect(frame.getByText('enabled_service')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and incorrectly fill the ports in Firewall step', async () => {
|
||||
await frame.getByPlaceholder('Add ports').fill('x');
|
||||
await frame.getByRole('button', { name: 'Add ports' }).click();
|
||||
await expect(
|
||||
frame
|
||||
.getByText(
|
||||
'Expected format: <port/port-name>:<protocol>. Example: 8080:tcp, ssh:tcp',
|
||||
)
|
||||
.nth(0),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and incorrectly fill the disabled services in Firewall step', async () => {
|
||||
await frame.getByPlaceholder('Add disabled service').fill('1');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await expect(
|
||||
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and incorrectly fill the enabled services in Firewall step', async () => {
|
||||
await frame.getByPlaceholder('Add enabled service').fill('ťčš');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
await expect(
|
||||
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Firewall step').click();
|
||||
|
||||
await frame.getByPlaceholder('Add ports').fill('90:tcp');
|
||||
await frame.getByRole('button', { name: 'Add ports' }).click();
|
||||
await frame.getByPlaceholder('Add disabled service').fill('x');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await frame.getByPlaceholder('Add enabled service').fill('y');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Close 80:tcp' }).click();
|
||||
await frame.getByRole('button', { name: 'Close enabled_service' }).click();
|
||||
await frame.getByRole('button', { name: 'Close disabled_service' }).click();
|
||||
|
||||
await expect(frame.getByText('90:tcp')).toBeVisible();
|
||||
await expect(frame.getByText('x').nth(0)).toBeVisible();
|
||||
await expect(frame.getByText('y').nth(0)).toBeVisible();
|
||||
|
||||
await expect(frame.getByText('80:tcp')).toBeHidden();
|
||||
await expect(frame.getByText('disabled_service')).toBeHidden();
|
||||
await expect(frame.getByText('enabled_service')).toBeHidden();
|
||||
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Firewall' }).click();
|
||||
|
||||
await expect(frame.getByText('90:tcp')).toBeVisible();
|
||||
await expect(frame.getByText('x').nth(0)).toBeVisible();
|
||||
await expect(frame.getByText('y').nth(0)).toBeVisible();
|
||||
|
||||
await expect(frame.getByText('80:tcp')).toBeHidden();
|
||||
await expect(frame.getByText('disabled_service')).toBeHidden();
|
||||
await expect(frame.getByText('enabled_service')).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Hostname customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
const hostname = 'testsystem';
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the Hostname step', async () => {
|
||||
await frame.getByRole('button', { name: 'Hostname' }).click();
|
||||
await frame.getByRole('textbox', { name: 'hostname input' }).fill(hostname);
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Hostname step').click();
|
||||
await frame.getByRole('textbox', { name: 'hostname input' }).click();
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'hostname input' })
|
||||
.fill(hostname + 'edited');
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Hostname' }).click();
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'hostname input' }),
|
||||
).toHaveValue(hostname + 'edited');
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Kernel customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the Kernel step', async () => {
|
||||
await frame.getByRole('button', { name: 'Kernel' }).click();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).click();
|
||||
await frame.getByRole('option', { name: 'kernel', exact: true }).click();
|
||||
await frame.getByPlaceholder('Add kernel argument').fill('rootwait');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await frame
|
||||
.getByPlaceholder('Add kernel argument')
|
||||
.fill('invalid$argument');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await expect(
|
||||
frame.getByText(
|
||||
'Expected format: <kernel-argument>. Example: console=tty0',
|
||||
),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Select kernel package').fill('new-package');
|
||||
await frame
|
||||
.getByRole('option', { name: 'Custom kernel package "new-' })
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'Warning alert: Custom kernel' }),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Clear input' }).first().click();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).click();
|
||||
await expect(
|
||||
frame.getByRole('option', { name: 'new-package' }),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Select kernel package').fill('f');
|
||||
await expect(
|
||||
frame.getByRole('option', {
|
||||
name: '"f" is not a valid kernel package name',
|
||||
}),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add kernel argument').fill('console=tty0');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await frame.getByPlaceholder('Add kernel argument').fill('xxnosmp');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await frame
|
||||
.getByPlaceholder('Add kernel argument')
|
||||
.fill('console=ttyS0,115200n8');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Kernel step').click();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).click();
|
||||
await frame.getByRole('option', { name: 'kernel', exact: true }).click();
|
||||
await frame.getByPlaceholder('Add kernel argument').fill('new=argument');
|
||||
await frame.getByRole('button', { name: 'Add kernel argument' }).click();
|
||||
await frame.getByRole('button', { name: 'Close xxnosmp' }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(frame);
|
||||
await frame.getByRole('button', { name: 'Kernel' }).click();
|
||||
await expect(frame.getByPlaceholder('Select kernel package')).toHaveValue(
|
||||
'kernel',
|
||||
);
|
||||
await expect(frame.getByText('rootwait')).toBeVisible();
|
||||
await expect(frame.getByText('console=tty0')).toBeVisible();
|
||||
await expect(frame.getByText('console=ttyS0,115200n8')).toBeVisible();
|
||||
await expect(frame.getByText('new=argument')).toBeVisible();
|
||||
await expect(frame.getByText('xxnosmp')).toBeHidden();
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Locale customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the Locale step', async () => {
|
||||
await frame.getByRole('button', { name: 'Locale' }).click();
|
||||
await frame.getByPlaceholder('Select a language').fill('fy');
|
||||
await frame
|
||||
.getByRole('option', { name: 'Western Frisian - Germany (fy_DE.UTF-8)' })
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
await frame
|
||||
.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
})
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
}),
|
||||
).toBeHidden();
|
||||
await frame.getByPlaceholder('Select a language').fill('fy');
|
||||
await frame
|
||||
.getByRole('option', { name: 'Western Frisian - Germany (fy_DE.UTF-8)' })
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
await frame.getByPlaceholder('Select a language').fill('aa');
|
||||
await frame
|
||||
.getByRole('option', { name: 'aa - Djibouti (aa_DJ.UTF-8)' })
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
|
||||
).toBeEnabled();
|
||||
await frame.getByPlaceholder('Select a language').fill('aa');
|
||||
await expect(
|
||||
frame.getByText(
|
||||
'aa - Djibouti (aa_DJ.UTF-8)Language already addedaa - Eritrea (aa_ER.UTF-8)aa - Ethiopia (aa_ET.UTF-8)',
|
||||
),
|
||||
).toBeAttached();
|
||||
await frame.getByPlaceholder('Select a language').fill('xxx');
|
||||
await expect(frame.getByText('No results found for')).toBeAttached();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).nth(1).click();
|
||||
await frame.getByPlaceholder('Select a keyboard').fill('ami');
|
||||
await frame.getByRole('option', { name: 'amiga-de' }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Locale step').click();
|
||||
await expect(
|
||||
frame.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
|
||||
).toBeEnabled();
|
||||
await frame.getByPlaceholder('Select a language').fill('aa');
|
||||
await frame
|
||||
.getByRole('option', { name: 'aa - Eritrea (aa_ER.UTF-8)' })
|
||||
.click();
|
||||
await expect(
|
||||
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' }),
|
||||
).toBeEnabled();
|
||||
await frame.getByRole('button', { name: 'Clear input' }).click();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).nth(1).click();
|
||||
await frame.getByRole('option', { name: 'ANSI-dvorak' }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Locale' }).click();
|
||||
await expect(
|
||||
frame.getByRole('button', {
|
||||
name: 'Close Western Frisian - Germany (fy_DE.UTF-8)',
|
||||
}),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
frame.getByRole('button', { name: 'Close aa - Djibouti (aa_DJ.UTF-8)' }),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
frame.getByRole('button', { name: 'Close aa - Eritrea (aa_ER.UTF-8)' }),
|
||||
).toBeEnabled();
|
||||
await expect(frame.getByPlaceholder('Select a keyboard')).toHaveValue(
|
||||
'ANSI-dvorak',
|
||||
);
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/cleanup';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import { ibFrame, navigateToLandingPage } from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with OpenSCAP customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
test.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Select RHEL 9 and go to optional steps in Wizard', async () => {
|
||||
await frame.getByRole('button', { name: 'Create image blueprint' }).click();
|
||||
await frame.getByTestId('release_select').click();
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'Red Hat Enterprise Linux (RHEL) 9 Full support ends: May 2027 | Maintenance',
|
||||
})
|
||||
.click();
|
||||
await frame.getByRole('checkbox', { name: 'Virtualization' }).click();
|
||||
await frame.getByRole('button', { name: 'Next' }).click();
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select only OpenSCAP, and check if dependencies are preselected', async () => {
|
||||
await frame.getByRole('button', { name: 'Compliance' }).click();
|
||||
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server This profile',
|
||||
})
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'File system configuration' })
|
||||
.click();
|
||||
await expect(
|
||||
frame
|
||||
.getByRole('row', {
|
||||
name: 'Draggable row draggable button /tmp xfs 1 GiB',
|
||||
})
|
||||
.getByRole('button')
|
||||
.nth(3),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Additional packages' }).click();
|
||||
await frame.getByRole('button', { name: 'Selected (8)' }).click();
|
||||
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'firewalld' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libpwquality' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libselinux' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'nftables' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'systemd-journal-remote' }),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Systemd services' }).click();
|
||||
await expect(
|
||||
frame.getByText('Required by OpenSCAPcrondfirewalldsystemd-journald'),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add masked service').fill('nftables');
|
||||
await frame.getByPlaceholder('Add masked service').press('Enter');
|
||||
await expect(
|
||||
frame.getByText('Masked service already exists'),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
|
||||
await expect(frame.getByText('nfs-server')).toBeVisible();
|
||||
await expect(frame.getByText('rpcbind')).toBeVisible();
|
||||
await expect(frame.getByText('avahi-daemon')).toBeVisible();
|
||||
await expect(frame.getByText('autofs')).toBeVisible();
|
||||
await expect(frame.getByText('bluetooth')).toBeVisible();
|
||||
await expect(frame.getByText('nftables')).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'Compliance' }).click();
|
||||
await expect(frame.getByText('Level 1 - Server')).toBeVisible();
|
||||
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
|
||||
await frame
|
||||
.getByRole('option', {
|
||||
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 2 - Server This profile',
|
||||
})
|
||||
.click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Kernel' }).click();
|
||||
|
||||
await expect(
|
||||
frame.getByText('Required by OpenSCAPaudit_backlog_limit=8192audit='),
|
||||
).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Additional packages' }).click();
|
||||
await frame.getByRole('button', { name: 'Selected (10)' }).click();
|
||||
await expect(frame.getByRole('gridcell', { name: 'aide' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'audit-libs' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'chrony' })).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'firewalld' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libpwquality' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'libselinux' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('gridcell', { name: 'nftables' }),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByRole('gridcell', { name: 'sudo' })).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Systemd services' }).click();
|
||||
await expect(
|
||||
frame.getByText(
|
||||
'Required by OpenSCAPauditdcrondfirewalldsystemd-journald',
|
||||
),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add masked service').fill('nftables');
|
||||
await frame.getByPlaceholder('Add masked service').press('Enter');
|
||||
await expect(
|
||||
frame.getByText('Masked service already exists'),
|
||||
).toBeVisible();
|
||||
await expect(frame.getByText('Required by OpenSCAPcupsnfs-')).toBeVisible();
|
||||
await expect(frame.getByText('nfs-server')).toBeVisible();
|
||||
await expect(frame.getByText('rpcbind')).toBeVisible();
|
||||
await expect(frame.getByText('avahi-daemon')).toBeVisible();
|
||||
await expect(frame.getByText('autofs')).toBeVisible();
|
||||
await expect(frame.getByText('bluetooth')).toBeVisible();
|
||||
await expect(frame.getByText('nftables')).toBeVisible();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async () => {
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async () => {
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async () => {
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Compliance' }).click();
|
||||
|
||||
await expect(frame.getByText('Level 2 - Server')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Systemd customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and correctly fill all of the service fields', async () => {
|
||||
await frame.getByRole('button', { name: 'Systemd services' }).click();
|
||||
|
||||
await frame
|
||||
.getByPlaceholder('Add disabled service')
|
||||
.fill('systemd-dis.service');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await expect(frame.getByText('systemd-dis.service')).toBeVisible();
|
||||
|
||||
await frame
|
||||
.getByPlaceholder('Add enabled service')
|
||||
.fill('systemd-en.service');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
await expect(frame.getByText('systemd-en.service')).toBeVisible();
|
||||
|
||||
await frame
|
||||
.getByPlaceholder('Add masked service')
|
||||
.fill('systemd-m.service');
|
||||
await frame.getByRole('button', { name: 'Add masked service' }).click();
|
||||
await expect(frame.getByText('systemd-m.service')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Select and incorrectly fill all of the service fields', async () => {
|
||||
await frame.getByPlaceholder('Add disabled service').fill('&&');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await expect(
|
||||
frame.getByText('Expected format: <service-name>. Example: sshd').nth(0),
|
||||
).toBeVisible();
|
||||
|
||||
await frame.getByPlaceholder('Add enabled service').fill('áá');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
await expect(
|
||||
frame.getByText('Expected format: <service-name>. Example: sshd').nth(1),
|
||||
).toBeVisible();
|
||||
|
||||
await frame.getByPlaceholder('Add masked service').fill('78');
|
||||
await frame.getByRole('button', { name: 'Add masked service' }).click();
|
||||
await expect(
|
||||
frame.getByText('Expected format: <service-name>. Example: sshd').nth(2),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Systemd services step').click();
|
||||
|
||||
await frame
|
||||
.getByPlaceholder('Add disabled service')
|
||||
.fill('disabled-service');
|
||||
await frame.getByRole('button', { name: 'Add disabled service' }).click();
|
||||
await frame.getByPlaceholder('Add enabled service').fill('enabled-service');
|
||||
await frame.getByRole('button', { name: 'Add enabled service' }).click();
|
||||
await frame.getByPlaceholder('Add masked service').fill('masked-service');
|
||||
await frame.getByRole('button', { name: 'Add masked service' }).click();
|
||||
|
||||
await frame
|
||||
.getByRole('button', { name: 'Close systemd-m.service' })
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Close systemd-en.service' })
|
||||
.click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Close systemd-dis.service' })
|
||||
.click();
|
||||
|
||||
await expect(frame.getByText('enabled-service')).toBeVisible();
|
||||
await expect(frame.getByText('disabled-service')).toBeVisible();
|
||||
await expect(frame.getByText('masked-service')).toBeVisible();
|
||||
|
||||
await expect(frame.getByText('systemd-en.service')).toBeHidden();
|
||||
await expect(frame.getByText('systemd-dis.service')).toBeHidden();
|
||||
await expect(frame.getByText('systemd-m.service')).toBeHidden();
|
||||
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await page.getByRole('button', { name: 'Systemd services' }).click();
|
||||
|
||||
await expect(frame.getByText('enabled-service')).toBeVisible();
|
||||
await expect(frame.getByText('disabled-service')).toBeVisible();
|
||||
await expect(frame.getByText('masked-service')).toBeVisible();
|
||||
|
||||
await expect(frame.getByText('systemd-en.service')).toBeHidden();
|
||||
await expect(frame.getByText('systemd-dis.service')).toBeHidden();
|
||||
await expect(frame.getByText('systemd-m.service')).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { test } from '../fixtures/customizations';
|
||||
import { isHosted } from '../helpers/helpers';
|
||||
import { ensureAuthenticated } from '../helpers/login';
|
||||
import {
|
||||
ibFrame,
|
||||
navigateToLandingPage,
|
||||
navigateToOptionalSteps,
|
||||
} from '../helpers/navHelpers';
|
||||
import {
|
||||
createBlueprint,
|
||||
deleteBlueprint,
|
||||
exportBlueprint,
|
||||
fillInDetails,
|
||||
fillInImageOutputGuest,
|
||||
importBlueprint,
|
||||
registerLater,
|
||||
} from '../helpers/wizardHelpers';
|
||||
|
||||
test('Create a blueprint with Timezone customization', async ({
|
||||
page,
|
||||
cleanup,
|
||||
}) => {
|
||||
const blueprintName = 'test-' + uuidv4();
|
||||
|
||||
// Delete the blueprint after the run fixture
|
||||
await cleanup.add(() => deleteBlueprint(page, blueprintName));
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
await test.step('Navigate to optional steps in Wizard', async () => {
|
||||
await navigateToOptionalSteps(frame);
|
||||
await registerLater(frame);
|
||||
});
|
||||
|
||||
await test.step('Select and fill the Timezone step', async () => {
|
||||
await frame.getByRole('button', { name: 'Timezone' }).click();
|
||||
await frame.getByPlaceholder('Select a timezone').fill('Canada');
|
||||
await frame.getByRole('option', { name: 'Canada/Saskatchewan' }).click();
|
||||
await frame.getByRole('button', { name: 'Clear input' }).first().click();
|
||||
await frame.getByPlaceholder('Select a timezone').fill('Europe');
|
||||
await frame.getByRole('option', { name: 'Europe/Stockholm' }).click();
|
||||
|
||||
await frame.getByPlaceholder('Add NTP servers').fill('0.nl.pool.ntp.org');
|
||||
await frame.getByRole('button', { name: 'Add NTP server' }).click();
|
||||
await expect(frame.getByText('0.nl.pool.ntp.org')).toBeVisible();
|
||||
await frame.getByPlaceholder('Add NTP servers').fill('0.nl.pool.ntp.org');
|
||||
await frame.getByRole('button', { name: 'Add NTP server' }).click();
|
||||
await expect(frame.getByText('NTP server already exists.')).toBeVisible();
|
||||
await frame.getByPlaceholder('Add NTP servers').fill('xxxx');
|
||||
await frame.getByRole('button', { name: 'Add NTP server' }).click();
|
||||
await expect(
|
||||
frame
|
||||
.getByText('Expected format: <ntp-server>. Example: time.redhat.com')
|
||||
.nth(0),
|
||||
).toBeVisible();
|
||||
await frame.getByPlaceholder('Add NTP servers').fill('0.cz.pool.ntp.org');
|
||||
await frame.getByRole('button', { name: 'Add NTP server' }).click();
|
||||
await expect(frame.getByText('0.cz.pool.ntp.org')).toBeVisible();
|
||||
await frame.getByPlaceholder('Add NTP servers').fill('0.de.pool.ntp.org');
|
||||
await frame.getByRole('button', { name: 'Add NTP server' }).click();
|
||||
await expect(frame.getByText('0.de.pool.ntp.org')).toBeVisible();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Close 0.cz.pool.ntp.org' })
|
||||
.click();
|
||||
await expect(frame.getByText('0.cz.pool.ntp.org')).toBeHidden();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
});
|
||||
|
||||
await test.step('Fill the BP details', async () => {
|
||||
await fillInDetails(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Create BP', async () => {
|
||||
await createBlueprint(frame, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Edit BP', async () => {
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByLabel('Revisit Timezone step').click();
|
||||
await expect(frame.getByText('Canada/Saskatchewan')).toBeHidden();
|
||||
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
|
||||
'Europe/Stockholm',
|
||||
);
|
||||
await frame.getByPlaceholder('Select a timezone').fill('Europe');
|
||||
await frame.getByRole('option', { name: 'Europe/Oslo' }).click();
|
||||
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
|
||||
'Europe/Oslo',
|
||||
);
|
||||
await expect(frame.getByText('0.nl.pool.ntp.org')).toBeVisible();
|
||||
await expect(frame.getByText('0.de.pool.ntp.org')).toBeVisible();
|
||||
await expect(frame.getByText('0.cz.pool.ntp.org')).toBeHidden();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
});
|
||||
|
||||
// This is for hosted service only as these features are not available in cockpit plugin
|
||||
await test.step('Export BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Exporting is not available in the plugin');
|
||||
await exportBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Import BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await importBlueprint(page, blueprintName);
|
||||
});
|
||||
|
||||
await test.step('Review imported BP', async (step) => {
|
||||
step.skip(!isHosted(), 'Importing is not available in the plugin');
|
||||
await fillInImageOutputGuest(page);
|
||||
await frame.getByRole('button', { name: 'Timezone' }).click();
|
||||
await expect(frame.getByPlaceholder('Select a timezone')).toHaveValue(
|
||||
'Europe/Oslo',
|
||||
);
|
||||
await expect(frame.getByText('0.nl.pool.ntp.org')).toBeVisible();
|
||||
await expect(frame.getByText('0.de.pool.ntp.org')).toBeVisible();
|
||||
await expect(frame.getByText('0.cz.pool.ntp.org')).toBeHidden();
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { test as oldTest } from '@playwright/test';
|
||||
|
||||
type WithCleanup = {
|
||||
cleanup: Cleanup;
|
||||
};
|
||||
|
||||
export interface Cleanup {
|
||||
add: (cleanupFn: () => Promise<unknown>) => symbol;
|
||||
runAndAdd: (cleanupFn: () => Promise<unknown>) => Promise<symbol>;
|
||||
remove: (key: symbol) => void;
|
||||
}
|
||||
|
||||
export const test = oldTest.extend<WithCleanup>({
|
||||
cleanup: async ({}, use) => {
|
||||
const cleanupFns: Map<symbol, () => Promise<unknown>> = new Map();
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
await use({
|
||||
add: (cleanupFn) => {
|
||||
const key = Symbol();
|
||||
cleanupFns.set(key, cleanupFn);
|
||||
return key;
|
||||
},
|
||||
runAndAdd: async (cleanupFn) => {
|
||||
await cleanupFn();
|
||||
|
||||
const key = Symbol();
|
||||
cleanupFns.set(key, cleanupFn);
|
||||
return key;
|
||||
},
|
||||
remove: (key) => {
|
||||
cleanupFns.delete(key);
|
||||
},
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Post-test cleanup',
|
||||
async () => {
|
||||
await Promise.all(Array.from(cleanupFns).map(([, fn]) => fn()));
|
||||
},
|
||||
{ box: true },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// This is a common fixture for the customizations tests
|
||||
import { mergeTests } from '@playwright/test';
|
||||
|
||||
import { test as cleanupTest } from './cleanup';
|
||||
import { test as popupTest } from './popupHandler';
|
||||
|
||||
// Combine the fixtures into one
|
||||
export const test = mergeTests(cleanupTest, popupTest);
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { test as base } from '@playwright/test';
|
||||
|
||||
import { closePopupsIfExist } from '../helpers/helpers';
|
||||
|
||||
export interface PopupHandlerFixture {
|
||||
popupHandler: void;
|
||||
}
|
||||
|
||||
// This fixture will close any popups that might get opened during the test execution
|
||||
export const test = base.extend<PopupHandlerFixture>({
|
||||
popupHandler: [
|
||||
async ({ page }, use) => {
|
||||
await closePopupsIfExist(page);
|
||||
await use(undefined);
|
||||
},
|
||||
{ auto: true },
|
||||
],
|
||||
});
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { test as setup } from '@playwright/test';
|
||||
|
||||
import { login, storeStorageStateAndToken } from './helpers/login';
|
||||
|
||||
setup.describe('Setup', () => {
|
||||
setup.describe.configure({ retries: 3 });
|
||||
|
||||
setup('Authenticate', async ({ page }) => {
|
||||
await login(page);
|
||||
await storeStorageStateAndToken(page);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { execSync } from 'child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
export const togglePreview = async (page: Page) => {
|
||||
const toggleSwitch = page.locator('#preview-toggle');
|
||||
|
||||
if (!(await toggleSwitch.isChecked())) {
|
||||
await toggleSwitch.click();
|
||||
}
|
||||
|
||||
const turnOnButton = page.getByRole('button', { name: 'Turn on' });
|
||||
if (await turnOnButton.isVisible()) {
|
||||
await turnOnButton.click();
|
||||
}
|
||||
|
||||
await expect(toggleSwitch).toBeChecked();
|
||||
};
|
||||
|
||||
export const isHosted = (): boolean => {
|
||||
return process.env.BASE_URL?.includes('redhat.com') || false;
|
||||
};
|
||||
|
||||
export const closePopupsIfExist = async (page: Page) => {
|
||||
const locatorsToCheck = [
|
||||
page.locator('.pf-v6-c-alert.notification-item button'), // This closes all toast pop-ups
|
||||
page.locator(`button[id^="pendo-close-guide-"]`), // This closes the pendo guide pop-up
|
||||
page.locator(`button[id="truste-consent-button"]`), // This closes the trusted consent pop-up
|
||||
page.getByLabel('close-notification'), // This closes a one off info notification (May be covered by the toast above, needs recheck.)
|
||||
page
|
||||
.locator('iframe[name="intercom-modal-frame"]')
|
||||
.contentFrame()
|
||||
.getByRole('button', { name: 'Close' }), // This closes the intercom pop-up
|
||||
page
|
||||
.locator('iframe[name="intercom-notifications-frame"]')
|
||||
.contentFrame()
|
||||
.getByRole('button', { name: 'Profile image for Rob Rob' })
|
||||
.last(), // This closes the intercom pop-up notification at the bottom of the screen, the last notification is displayed first if stacked (different from the modal popup handled above)
|
||||
];
|
||||
|
||||
for (const locator of locatorsToCheck) {
|
||||
await page.addLocatorHandler(locator, async () => {
|
||||
await locator.first().click({ timeout: 10_000, noWaitAfter: true }); // There can be multiple toast pop-ups
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// copied over from constants
|
||||
const ON_PREM_RELEASES = new Map([
|
||||
['centos-10', 'CentOS Stream 10'],
|
||||
['fedora-41', 'Fedora Linux 41'],
|
||||
['fedora-42', 'Fedora Linux 42'],
|
||||
['rhel-10', 'Red Hat Enterprise Linux (RHEL) 10'],
|
||||
]);
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export const getHostDistroName = (): string => {
|
||||
const osRelData = readFileSync('/etc/os-release');
|
||||
const lines = osRelData
|
||||
.toString('utf-8')
|
||||
.split('\n')
|
||||
.filter((l) => l !== '');
|
||||
const osRel = {};
|
||||
|
||||
for (const l of lines) {
|
||||
const lineData = l.split('=');
|
||||
(osRel as any)[lineData[0]] = lineData[1].replace(/"/g, '');
|
||||
}
|
||||
|
||||
// strip minor version from rhel
|
||||
const distro = ON_PREM_RELEASES.get(
|
||||
`${(osRel as any)['ID']}-${(osRel as any)['VERSION_ID'].split('.')[0]}`,
|
||||
);
|
||||
|
||||
if (distro === undefined) {
|
||||
/* eslint-disable no-console */
|
||||
console.error('getHostDistroName failed, os-release config:', osRel);
|
||||
throw new Error('getHostDistroName failed, distro undefined');
|
||||
}
|
||||
|
||||
return distro;
|
||||
};
|
||||
|
||||
export const getHostArch = (): string => {
|
||||
return execSync('uname -m').toString('utf-8').replace(/\s/g, '');
|
||||
};
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
import path from 'path';
|
||||
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { closePopupsIfExist, isHosted, togglePreview } from './helpers';
|
||||
import { ibFrame } from './navHelpers';
|
||||
|
||||
/**
|
||||
* Logs in to either Cockpit or Console, will distinguish between them based on the environment
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const login = async (page: Page) => {
|
||||
if (!process.env.PLAYWRIGHT_USER || !process.env.PLAYWRIGHT_PASSWORD) {
|
||||
throw new Error('user or password not set in environment');
|
||||
}
|
||||
|
||||
const user = process.env.PLAYWRIGHT_USER;
|
||||
const password = process.env.PLAYWRIGHT_PASSWORD;
|
||||
|
||||
if (isHosted()) {
|
||||
return loginConsole(page, user, password);
|
||||
}
|
||||
return loginCockpit(page, user, password);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the user is already authenticated, if not, logs them in
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const ensureAuthenticated = async (page: Page) => {
|
||||
// Navigate to the target page
|
||||
if (isHosted()) {
|
||||
await page.goto('/insights/image-builder/landing');
|
||||
} else {
|
||||
await page.goto('/cockpit-image-builder');
|
||||
}
|
||||
|
||||
// Check for authentication success indicator
|
||||
const successIndicator = isHosted()
|
||||
? page.getByRole('heading', { name: 'All images' })
|
||||
: ibFrame(page).getByRole('heading', { name: 'All images' });
|
||||
|
||||
let isAuthenticated = false;
|
||||
try {
|
||||
// Give it a 30 second period to load, it's less expensive than having to rerun the test
|
||||
await expect(successIndicator).toBeVisible({ timeout: 30000 });
|
||||
isAuthenticated = true;
|
||||
} catch {
|
||||
isAuthenticated = false;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// Not authenticated, need to login
|
||||
await login(page);
|
||||
}
|
||||
};
|
||||
|
||||
const loginCockpit = async (page: Page, user: string, password: string) => {
|
||||
await page.goto('/cockpit-image-builder');
|
||||
|
||||
await page.getByRole('textbox', { name: 'User name' }).fill(user);
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(password);
|
||||
await page.getByRole('button', { name: 'Log in' }).click();
|
||||
|
||||
// image-builder lives inside an iframe
|
||||
const frame = ibFrame(page);
|
||||
|
||||
try {
|
||||
// Check if the user already has administrative access
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Administrative access' }),
|
||||
).toBeVisible();
|
||||
} catch {
|
||||
// If not, try to gain it
|
||||
// cockpit-image-builder needs superuser, expect an error message
|
||||
// when the user does not have admin priviliges
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'Access is limited' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Limited access' }).click();
|
||||
|
||||
// different popup opens based on type of account (can be passwordless)
|
||||
const authenticateButton = page.getByRole('button', {
|
||||
name: 'Authenticate',
|
||||
});
|
||||
const closeButton = page.getByText('Close');
|
||||
await expect(authenticateButton.or(closeButton)).toBeVisible();
|
||||
|
||||
if (await authenticateButton.isVisible()) {
|
||||
// with password
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(password);
|
||||
await authenticateButton.click();
|
||||
}
|
||||
if (await closeButton.isVisible()) {
|
||||
// passwordless
|
||||
await closeButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
// expect to have administrative access
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Administrative access' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'All images' }),
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
const loginConsole = async (page: Page, user: string, password: string) => {
|
||||
await closePopupsIfExist(page);
|
||||
await page.goto('/insights/image-builder/landing');
|
||||
await page.getByRole('textbox', { name: 'Red Hat login' }).fill(user);
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(password);
|
||||
await page.getByRole('button', { name: 'Log in' }).click();
|
||||
await togglePreview(page);
|
||||
await expect(page.getByRole('heading', { name: 'All images' })).toBeVisible();
|
||||
};
|
||||
|
||||
export const storeStorageStateAndToken = async (page: Page) => {
|
||||
const { cookies } = await page
|
||||
.context()
|
||||
.storageState({ path: path.join(__dirname, '../../.auth/user.json') });
|
||||
if (isHosted()) {
|
||||
// For hosted service, look for cs_jwt token
|
||||
process.env.TOKEN = `Bearer ${
|
||||
cookies.find((cookie) => cookie.name === 'cs_jwt')?.value
|
||||
}`;
|
||||
} else {
|
||||
// For Cockpit, we don't need a TOKEN but we can still store it for consistency
|
||||
const cockpitCookie = cookies.find((cookie) => cookie.name === 'cockpit');
|
||||
if (cockpitCookie) {
|
||||
process.env.TOKEN = cockpitCookie.value;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||
await page.waitForTimeout(100);
|
||||
};
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import { expect, FrameLocator, Page } from '@playwright/test';
|
||||
|
||||
import { getHostArch, getHostDistroName, isHosted } from './helpers';
|
||||
|
||||
/**
|
||||
* Opens the wizard, fills out the "Image Output" step, and navigates to the optional steps
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const navigateToOptionalSteps = async (page: Page | FrameLocator) => {
|
||||
await page.getByRole('button', { name: 'Create image blueprint' }).click();
|
||||
if (!isHosted()) {
|
||||
// wait until the distro and architecture aligns with the host
|
||||
await expect(page.getByTestId('release_select')).toHaveText(
|
||||
getHostDistroName(),
|
||||
);
|
||||
await expect(page.getByTestId('arch_select')).toHaveText(getHostArch());
|
||||
}
|
||||
await page.getByRole('checkbox', { name: 'Virtualization' }).click();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the FrameLocator object in case we are using cockpit plugin, else it returns the page object
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const ibFrame = (page: Page): FrameLocator | Page => {
|
||||
if (isHosted()) {
|
||||
return page;
|
||||
}
|
||||
return page
|
||||
.locator('iframe[name="cockpit1\\:localhost\\/cockpit-image-builder"]')
|
||||
.contentFrame();
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the landing page of the Image Builder
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const navigateToLandingPage = async (page: Page) => {
|
||||
if (isHosted()) {
|
||||
await page.goto('/insights/image-builder/landing');
|
||||
} else {
|
||||
await page.goto('/cockpit-image-builder');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
import { expect, FrameLocator, type Page, test } from '@playwright/test';
|
||||
|
||||
import { closePopupsIfExist, isHosted } from './helpers';
|
||||
import { ibFrame, navigateToLandingPage } from './navHelpers';
|
||||
|
||||
/**
|
||||
* Clicks the create button, handles the modal, clicks the button again and selecets the BP in the list
|
||||
* @param page - the page object
|
||||
* @param blueprintName - the name of the created blueprint
|
||||
*/
|
||||
export const createBlueprint = async (
|
||||
page: Page | FrameLocator,
|
||||
blueprintName: string,
|
||||
) => {
|
||||
await page.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).first().click();
|
||||
await page.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
await page.getByRole('textbox', { name: 'Search input' }).fill(blueprintName);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await page.locator(`button[id="${blueprintName}"]`).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill in the "Details" step in the wizard
|
||||
* This method assumes that the "Details" step is ENABLED!
|
||||
* After filling the step, it will click the "Next" button
|
||||
* Description defaults to "Testing blueprint"
|
||||
* @param page - the page object
|
||||
* @param blueprintName - the name of the blueprint to create
|
||||
*/
|
||||
export const fillInDetails = async (
|
||||
page: Page | FrameLocator,
|
||||
blueprintName: string,
|
||||
) => {
|
||||
await page.getByRole('listitem').filter({ hasText: 'Details' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Blueprint name' })
|
||||
.fill(blueprintName);
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Blueprint description' })
|
||||
.fill('Testing blueprint');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Select "Register later" option in the wizard
|
||||
* This function executes only on the hosted service
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const registerLater = async (page: Page | FrameLocator) => {
|
||||
if (isHosted()) {
|
||||
await page.getByRole('button', { name: 'Register' }).click();
|
||||
await page.getByRole('radio', { name: 'Register later' }).click();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill in the image output step in the wizard by selecting the Guest Image
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const fillInImageOutputGuest = async (page: Page | FrameLocator) => {
|
||||
await page.getByRole('checkbox', { name: 'Virtualization' }).click();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the blueprint with the given name
|
||||
* Will locate to the Image Builder page and search for the blueprint first
|
||||
* If the blueprint is not found, it will fail gracefully
|
||||
* @param page - the page object
|
||||
* @param blueprintName - the name of the blueprint to delete
|
||||
*/
|
||||
export const deleteBlueprint = async (page: Page, blueprintName: string) => {
|
||||
// Since new browser is opened during the BP cleanup, we need to call the popup closer again
|
||||
await closePopupsIfExist(page);
|
||||
await test.step(
|
||||
'Delete the blueprint with name: ' + blueprintName,
|
||||
async () => {
|
||||
// Locate back to the Image Builder page every time because the test can fail at any stage
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(blueprintName);
|
||||
// Check if no blueprints found -> that means no blueprint was created -> fail gracefully and do not raise error
|
||||
try {
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'No blueprints found' }),
|
||||
).toBeVisible({ timeout: 5_000 }); // Shorter timeout to avoid hanging uncessarily
|
||||
return; // Fail gracefully, no blueprint to delete
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (error) {
|
||||
// If the No BP heading was not found, it means the blueprint (possibly) was created -> continue with deletion
|
||||
}
|
||||
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${blueprintName}"]`).click();
|
||||
await frame.getByRole('button', { name: 'Menu toggle' }).click();
|
||||
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'Delete' }).click();
|
||||
},
|
||||
{ box: true },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Export the blueprint
|
||||
* This function executes only on the hosted service
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const exportBlueprint = async (page: Page, blueprintName: string) => {
|
||||
if (isHosted()) {
|
||||
await page.getByRole('button', { name: 'Menu toggle' }).click();
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page
|
||||
.getByRole('menuitem', { name: 'Download blueprint (.json)' })
|
||||
.click();
|
||||
const download = await downloadPromise;
|
||||
await download.saveAs('../../downloads/' + blueprintName + '.json');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Import the blueprint
|
||||
* This function executes only on the hosted service
|
||||
* @param page - the page object
|
||||
*/
|
||||
export const importBlueprint = async (
|
||||
page: Page | FrameLocator,
|
||||
blueprintName: string,
|
||||
) => {
|
||||
if (isHosted()) {
|
||||
await page.getByRole('button', { name: 'Import' }).click();
|
||||
const dragBoxSelector = page.getByRole('presentation').first();
|
||||
await dragBoxSelector
|
||||
.locator('input[type=file]')
|
||||
.setInputFiles('../../downloads/' + blueprintName + '.json');
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'File upload' }),
|
||||
).not.toBeEmpty();
|
||||
await page.getByRole('button', { name: 'Review and Finish' }).click();
|
||||
}
|
||||
};
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
|
||||
import TOML from '@ltd/j-toml';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { closePopupsIfExist, isHosted } from './helpers/helpers';
|
||||
import { ensureAuthenticated } from './helpers/login';
|
||||
import { ibFrame, navigateToLandingPage } from './helpers/navHelpers';
|
||||
|
||||
test.describe.serial('test', () => {
|
||||
const blueprintName = uuidv4();
|
||||
test('create blueprint', async ({ page }) => {
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
|
||||
frame.getByRole('heading', { name: 'Images About image builder' });
|
||||
frame.getByRole('heading', { name: 'Blueprints' });
|
||||
await frame.getByTestId('blueprints-create-button').click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Image output' });
|
||||
await frame
|
||||
.getByRole('checkbox', { name: /Virtualization guest image/i })
|
||||
.click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', {
|
||||
name: 'Register systems using this image',
|
||||
});
|
||||
await page.getByRole('radio', { name: /Register later/i }).click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
frame.getByRole('heading', { name: 'Compliance' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'File system configuration' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', { name: 'Repository snapshot' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
frame.getByRole('heading', { name: 'Custom repositories' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
frame.getByRole('heading', { name: 'Additional packages' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Users' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Timezone' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Locale' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Hostname' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Kernel' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Firewall' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Systemd services' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', { name: 'Ansible Automation Platform' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
if (isHosted()) {
|
||||
frame.getByRole('heading', { name: 'First boot configuration' });
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
}
|
||||
|
||||
frame.getByRole('heading', { name: 'Details' });
|
||||
await frame.getByTestId('blueprint').fill(blueprintName);
|
||||
await expect(frame.getByTestId('blueprint')).toHaveValue(blueprintName);
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
await frame.getByTestId('close-button-saveandbuild-modal').click();
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
|
||||
await expect(
|
||||
frame.locator('.pf-v6-c-card__title-text').getByText(
|
||||
// if the name is too long, the blueprint card will have a truncated name.
|
||||
blueprintName.length > 24
|
||||
? blueprintName.slice(0, 24) + '...'
|
||||
: blueprintName,
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('edit blueprint', async ({ page }) => {
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// package searching is really slow the first time in cockpit
|
||||
if (!isHosted()) {
|
||||
test.setTimeout(300000);
|
||||
}
|
||||
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(blueprintName);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${blueprintName}"]`).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'Additional packages' }).click();
|
||||
await frame
|
||||
.getByTestId('packages-search-input')
|
||||
.locator('input')
|
||||
.fill('osbuild-composer');
|
||||
frame.getByTestId('packages-table').getByText('Searching');
|
||||
frame.getByRole('gridcell', { name: 'osbuild-composer' }).first();
|
||||
await frame.getByRole('checkbox', { name: 'Select row 0' }).check();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame.getByRole('button', { name: 'About packages' }).click();
|
||||
frame.getByRole('gridcell', { name: 'osbuild-composer' });
|
||||
await frame.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
await frame
|
||||
.getByRole('button', { name: 'Save changes to blueprint' })
|
||||
.click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'About packages' }).click();
|
||||
frame.getByRole('gridcell', { name: 'osbuild-composer' });
|
||||
await frame.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Cancel', exact: true }).click();
|
||||
frame.getByRole('heading', { name: 'All images' });
|
||||
});
|
||||
|
||||
test('build blueprint', async ({ page }) => {
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(blueprintName);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${blueprintName}"]`).click();
|
||||
await frame.getByTestId('blueprint-build-image-menu-option').click();
|
||||
|
||||
// make sure the image is present
|
||||
await frame
|
||||
.getByTestId('images-table')
|
||||
.getByRole('button', { name: 'Details' })
|
||||
.click();
|
||||
frame.getByText('Build Information');
|
||||
});
|
||||
|
||||
test('delete blueprint', async ({ page }) => {
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
const frame = await ibFrame(page);
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(blueprintName);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${blueprintName}"]`).click();
|
||||
await frame.getByRole('button', { name: /blueprint menu toggle/i }).click();
|
||||
await frame.getByRole('menuitem', { name: 'Delete blueprint' }).click();
|
||||
await frame.getByRole('button', { name: 'Delete' }).click();
|
||||
});
|
||||
|
||||
test('cockpit worker config', async ({ page }) => {
|
||||
if (isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
await page.goto('/cockpit-image-builder');
|
||||
const frame = ibFrame(page);
|
||||
|
||||
const header = frame.getByText('Configure AWS Uploads');
|
||||
if (!(await header.isVisible())) {
|
||||
await frame
|
||||
.getByRole('button', { name: 'Configure Cloud Providers' })
|
||||
.click();
|
||||
await expect(header).toBeVisible();
|
||||
}
|
||||
|
||||
const bucket = 'cockpit-ib-playwright-bucket';
|
||||
const credentials = '/test/credentials';
|
||||
const switchInput = frame.locator('#aws-config-switch');
|
||||
await expect(switchInput).toBeVisible();
|
||||
|
||||
// introduce a wait time, since it takes some time to load the
|
||||
// worker config file.
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// If this test fails for any reason, the config should already be loaded
|
||||
// and visible on the retury. If it is go back to the landing page
|
||||
if (await switchInput.isChecked()) {
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'All images' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
const switchToggle = frame.locator('.pf-v6-c-switch');
|
||||
await switchToggle.click();
|
||||
|
||||
await frame
|
||||
.getByPlaceholder('AWS bucket')
|
||||
// this doesn't need to exist, we're just testing that
|
||||
// the form works as expected
|
||||
.fill(bucket);
|
||||
await frame.getByPlaceholder('Path to AWS credentials').fill(credentials);
|
||||
await frame.getByRole('button', { name: 'Submit' }).click();
|
||||
await expect(
|
||||
frame.getByRole('heading', { name: 'All images' }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
await frame
|
||||
.getByRole('button', { name: 'Configure Cloud Providers' })
|
||||
.click();
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// introduce a wait time, since it takes some time to load the
|
||||
// worker config file.
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
await expect(frame.locator('#aws-config-switch')).toBeChecked();
|
||||
|
||||
await expect(frame.getByPlaceholder('AWS bucket')).toHaveValue(bucket);
|
||||
await expect(frame.getByPlaceholder('Path to AWS credentials')).toHaveValue(
|
||||
credentials,
|
||||
);
|
||||
await frame.getByRole('button', { name: 'Cancel' }).click();
|
||||
|
||||
const config = readFileSync('/etc/osbuild-worker/osbuild-worker.toml');
|
||||
// this is for testing, the field `aws` should exist
|
||||
// eslint-disable-next-line
|
||||
const parsed = TOML.parse(config) as any;
|
||||
expect(parsed.aws?.bucket).toBe(bucket);
|
||||
expect(parsed.aws?.credentials).toBe(credentials);
|
||||
});
|
||||
|
||||
const cockpitBlueprintname = uuidv4();
|
||||
test('cockpit cloud upload', async ({ page }) => {
|
||||
if (isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureAuthenticated(page);
|
||||
await closePopupsIfExist(page);
|
||||
// Navigate to IB landing page and get the frame
|
||||
await navigateToLandingPage(page);
|
||||
await page.goto('/cockpit-image-builder');
|
||||
const frame = ibFrame(page);
|
||||
|
||||
frame.getByRole('heading', { name: 'Images About image builder' });
|
||||
frame.getByRole('heading', { name: 'Blueprints' });
|
||||
await frame.getByTestId('blueprints-create-button').click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Image output' });
|
||||
// the first card should be the AWS card
|
||||
await frame.locator('.pf-v6-c-card').first().click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
await frame.getByRole('button', { name: 'Review and finish' }).click();
|
||||
await frame.getByRole('button', { name: 'Back', exact: true }).click();
|
||||
|
||||
frame.getByRole('heading', { name: 'Details' });
|
||||
await frame.getByTestId('blueprint').fill(cockpitBlueprintname);
|
||||
await expect(frame.getByTestId('blueprint')).toHaveValue(
|
||||
cockpitBlueprintname,
|
||||
);
|
||||
await frame.getByRole('button', { name: 'Next', exact: true }).click();
|
||||
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
await frame.getByTestId('close-button-saveandbuild-modal').click();
|
||||
await frame.getByRole('button', { name: 'Create blueprint' }).click();
|
||||
|
||||
await frame
|
||||
.getByRole('textbox', { name: 'Search input' })
|
||||
.fill(cockpitBlueprintname);
|
||||
// the clickable blueprint cards are a bit awkward, so use the
|
||||
// button's id instead
|
||||
await frame.locator(`button[id="${cockpitBlueprintname}"]`).click();
|
||||
await frame.getByTestId('blueprint-build-image-menu-option').click();
|
||||
|
||||
// make sure the image is present
|
||||
await frame
|
||||
.getByTestId('images-table')
|
||||
.getByRole('button', { name: 'Details' })
|
||||
.click();
|
||||
frame.getByText('Build Information');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
PLAYWRIGHT_USER="" # Required
|
||||
PLAYWRIGHT_PASSWORD="" # Required
|
||||
BASE_URL="https://stage.foo.redhat.com:1337" # Required
|
||||
CI="" # This is set to true for CI jobs, if checking for CI do !!process.env.CI
|
||||
TOKEN="" # This is handled programmatically.
|
||||
PROXY="" # Set this if running directly against stage (not using "yarn local")
|
||||
52
pr_check.sh
Executable file
52
pr_check.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
# --------------------------------------------
|
||||
# Export vars for helper scripts to use
|
||||
# --------------------------------------------
|
||||
# name of app-sre "application" folder this component lives in; needs to match for quay
|
||||
export COMPONENT_NAME="image-builder-frontend"
|
||||
# IMAGE should match the quay repo set by app.yaml in app-interface
|
||||
export IMAGE="quay.io/cloudservices/image-builder-frontend"
|
||||
export WORKSPACE=${WORKSPACE:-$APP_ROOT} # if running in jenkins, use the build's workspace
|
||||
export APP_ROOT=$(pwd)
|
||||
#16 is the default Node version. Change this to override it.
|
||||
export NODE_BUILD_VERSION=16
|
||||
COMMON_BUILDER=https://raw.githubusercontent.com/RedHatInsights/insights-frontend-builder-common/master
|
||||
|
||||
# --------------------------------------------
|
||||
# Options that must be configured by app owner
|
||||
# --------------------------------------------
|
||||
export IQE_PLUGINS="image-builder"
|
||||
export IQE_CJI_TIMEOUT="60m"
|
||||
export IQE_MARKER_EXPRESSION="ui"
|
||||
export IQE_SELENIUM="true"
|
||||
export IQE_ENV="ephemeral"
|
||||
export IQE_IMAGE_TAG="image-builder"
|
||||
export RESERVE_DURATION="2h"
|
||||
|
||||
# bootstrap bonfire and it's config
|
||||
CICD_URL=https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd
|
||||
curl -s "$CICD_URL"/bootstrap.sh > .cicd_bootstrap.sh && source .cicd_bootstrap.sh
|
||||
|
||||
# # source is preferred to | bash -s in this case to avoid a subshell
|
||||
source <(curl -sSL $COMMON_BUILDER/src/frontend-build.sh)
|
||||
|
||||
# reserve ephemeral namespace
|
||||
export DEPLOY_FRONTENDS="true"
|
||||
export EXTRA_DEPLOY_ARGS="provisioning sources content-sources rhsm-api-proxy --set-template-ref rhsm-api-proxy=master"
|
||||
export APP_NAME="image-builder-crc"
|
||||
export DEPLOY_TIMEOUT="1200"
|
||||
export REF_ENV="insights-stage"
|
||||
|
||||
source "$CICD_ROOT"/deploy_ephemeral_env.sh
|
||||
|
||||
# Run smoke tests using a ClowdJobInvocation (preferred)
|
||||
# The contents of this script can be found at:
|
||||
# https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd/cji_smoke_test.sh
|
||||
export COMPONENT_NAME="image-builder"
|
||||
source "$CICD_ROOT"/cji_smoke_test.sh
|
||||
|
||||
# Post a comment with test run IDs to the PR
|
||||
# The contents of this script can be found at:
|
||||
# https://raw.githubusercontent.com/RedHatInsights/bonfire/master/cicd/post_test_results.sh
|
||||
source "$CICD_ROOT"/post_test_results.sh
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"github>konflux-ci/mintmaker//config/renovate/renovate.json"
|
||||
],
|
||||
"schedule": [
|
||||
"on Monday after 3am and before 10am"
|
||||
],
|
||||
"ignorePaths": [
|
||||
".pre-commit-config.yaml"
|
||||
]
|
||||
}
|
||||
BIN
schutzbot/RH-IT-Root-CA.keystore
Normal file
BIN
schutzbot/RH-IT-Root-CA.keystore
Normal file
Binary file not shown.
|
|
@ -1,56 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Dumps details about the instance running the CI job.
|
||||
|
||||
PRIMARY_IP=$(ip route get 8.8.8.8 | head -n 1 | cut -d' ' -f7)
|
||||
EXTERNAL_IP=$(curl --retry 5 -s -4 icanhazip.com)
|
||||
PTR=$(curl --retry 5 -s -4 icanhazptr.com)
|
||||
CPUS=$(nproc)
|
||||
MEM=$(free -m | grep -oP '\d+' | head -n 1)
|
||||
DISK=$(df --output=size -h / | sed '1d;s/[^0-9]//g')
|
||||
HOSTNAME=$(uname -n)
|
||||
USER=$(whoami)
|
||||
ARCH=$(uname -m)
|
||||
KERNEL=$(uname -r)
|
||||
|
||||
echo -e "\033[0;36m"
|
||||
cat << EOF
|
||||
------------------------------------------------------------------------------
|
||||
CI MACHINE SPECS
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
Hostname: ${HOSTNAME}
|
||||
User: ${USER}
|
||||
Primary IP: ${PRIMARY_IP}
|
||||
External IP: ${EXTERNAL_IP}
|
||||
Reverse DNS: ${PTR}
|
||||
CPUs: ${CPUS}
|
||||
RAM: ${MEM} GB
|
||||
DISK: ${DISK} GB
|
||||
ARCH: ${ARCH}
|
||||
KERNEL: ${KERNEL}
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
EOF
|
||||
echo -e "\033[0m"
|
||||
|
||||
echo "List of system repositories:"
|
||||
sudo yum repolist -v
|
||||
|
||||
echo "------------------------------------------------------------------------------"
|
||||
|
||||
echo "List of installed packages:"
|
||||
rpm -qa | sort
|
||||
echo "------------------------------------------------------------------------------"
|
||||
|
||||
# gcp runners don't use cloud-init and some of the images have python36 installed
|
||||
if [[ "$RUNNER" != *"gcp"* ]];then
|
||||
# Ensure cloud-init has completely finished on the instance. This ensures that
|
||||
# the instance is fully ready to go.
|
||||
while true; do
|
||||
if [[ -f /var/lib/cloud/instance/boot-finished ]]; then
|
||||
break
|
||||
fi
|
||||
echo -e "\n🤔 Waiting for cloud-init to finish running..."
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source /etc/os-release
|
||||
|
||||
sudo dnf install -y \
|
||||
libappstream-glib
|
||||
|
||||
if [[ "$ID" == rhel && ${VERSION_ID%.*} == 10 ]]; then
|
||||
sudo dnf install -y nodejs-npm \
|
||||
sqlite # node fails to pull this in
|
||||
elif [[ "$ID" == rhel ]]; then
|
||||
sudo dnf install -y npm
|
||||
elif [[ "$ID" == fedora ]]; then
|
||||
sudo dnf install -y \
|
||||
nodejs-npm \
|
||||
sqlite \
|
||||
gettext
|
||||
fi
|
||||
|
||||
npm ci
|
||||
|
||||
make rpm
|
||||
|
||||
sudo dnf install -y rpmbuild/RPMS/noarch/*rpm
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
summary: run playwright tests
|
||||
test: ./playwright_tests.sh
|
||||
require:
|
||||
- cockpit-image-builder
|
||||
- podman
|
||||
- nodejs
|
||||
- nodejs-npm
|
||||
duration: 30m
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
TMT_SOURCE_DIR=${TMT_SOURCE_DIR:-}
|
||||
if [ -n "$TMT_SOURCE_DIR" ]; then
|
||||
# Move to the directory with sources
|
||||
cd "${TMT_SOURCE_DIR}/cockpit-image-builder"
|
||||
npm ci
|
||||
elif [ "${CI:-}" != "true" ]; then
|
||||
# packit drops us into the schutzbot directory
|
||||
cd ../
|
||||
npm ci
|
||||
fi
|
||||
|
||||
sudo systemctl enable --now cockpit.socket
|
||||
|
||||
sudo useradd admin -p "$(openssl passwd foobar)"
|
||||
sudo usermod -aG wheel admin
|
||||
echo "admin ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/admin-nopasswd"
|
||||
|
||||
function upload_artifacts {
|
||||
if [ -n "${TMT_TEST_DATA:-}" ]; then
|
||||
mv playwright-report "$TMT_TEST_DATA"/playwright-report
|
||||
else
|
||||
USER="$(whoami)"
|
||||
sudo chown -R "$USER:$USER" playwright-report
|
||||
mv playwright-report /tmp/artifacts/
|
||||
fi
|
||||
}
|
||||
trap upload_artifacts EXIT
|
||||
|
||||
# to make package search work, the cdn repositories need to be replaced
|
||||
# with the nightly repositories
|
||||
|
||||
sudo mkdir -p /etc/osbuild-composer/repositories
|
||||
|
||||
cat <<EOF | sudo tee -a /etc/osbuild-composer/repositories/rhel-9.json
|
||||
{
|
||||
"x86_64": [
|
||||
{
|
||||
"name": "baseos",
|
||||
"baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9/compose/BaseOS/x86_64/os/",
|
||||
"check_gpg": false
|
||||
},
|
||||
{
|
||||
"name": "appstream",
|
||||
"baseurl": "http://download.devel.redhat.com/rhel-9/nightly/RHEL-9/latest-RHEL-9/compose/AppStream/x86_64/os/",
|
||||
"check_gpg": false
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
cat <<EOF | sudo tee -a /etc/osbuild-composer/repositories/rhel-10.json
|
||||
{
|
||||
"x86_64": [
|
||||
{
|
||||
"name": "baseos",
|
||||
"baseurl": "http://download.devel.redhat.com/rhel-10/nightly/RHEL-10/latest-RHEL-10/compose/BaseOS/x86_64/os/",
|
||||
"check_gpg": false
|
||||
},
|
||||
{
|
||||
"name": "appstream",
|
||||
"baseurl": "http://download.devel.redhat.com/rhel-10/nightly/RHEL-10/latest-RHEL-10/compose/AppStream/x86_64/os/",
|
||||
"check_gpg": false
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo systemctl enable --now osbuild-composer.socket osbuild-local-worker.socket
|
||||
sudo systemctl start osbuild-worker@1
|
||||
|
||||
sudo podman run \
|
||||
-e "PLAYWRIGHT_HTML_OPEN=never" \
|
||||
-e "CI=true" \
|
||||
-e "PLAYWRIGHT_USER=admin" \
|
||||
-e "PLAYWRIGHT_PASSWORD=foobar" \
|
||||
-e "CURRENTS_PROJECT_ID=${CURRENTS_PROJECT_ID:-}" \
|
||||
-e "CURRENTS_RECORD_KEY=${CURRENTS_RECORD_KEY:-}" \
|
||||
--net=host \
|
||||
-v "$PWD:/tests" \
|
||||
-v '/etc:/etc' \
|
||||
-v '/etc/os-release:/etc/os-release' \
|
||||
--privileged \
|
||||
--rm \
|
||||
--init \
|
||||
mcr.microsoft.com/playwright:v1.51.1-noble \
|
||||
/bin/sh -c "cd tests && npx -y playwright@1.51.1 test"
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# use tee, otherwise shellcheck complains
|
||||
sudo journalctl --boot | tee journal-log >/dev/null
|
||||
|
||||
# copy journal to artifacts folder which is then uploaded to secure S3 location
|
||||
cp journal-log "${ARTIFACTS:-/tmp/artifacts}"
|
||||
30
schutzbot/sonarqube.sh
Executable file
30
schutzbot/sonarqube.sh
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
SONAR_SCANNER_CLI_VERSION=${SONAR_SCANNER_CLI_VERSION:-4.6.2.2472}
|
||||
|
||||
export SONAR_SCANNER_OPTS="-Djavax.net.ssl.trustStore=schutzbot/RH-IT-Root-CA.keystore -Djavax.net.ssl.trustStorePassword=$KEYSTORE_PASS"
|
||||
sudo dnf install -y unzip nodejs
|
||||
curl "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_CLI_VERSION-linux.zip" -o sonar-scanner-cli.zip
|
||||
unzip -q sonar-scanner-cli.zip
|
||||
|
||||
SONAR_SCANNER_CMD="sonar-scanner-$SONAR_SCANNER_CLI_VERSION-linux/bin/sonar-scanner"
|
||||
SCANNER_OPTS="-Dsonar.projectKey=osbuild:image-builder-frontend -Dsonar.sources=. -Dsonar.host.url=https://sonarqube.corp.redhat.com -Dsonar.login=$SONAR_SCANNER_TOKEN"
|
||||
|
||||
# add options for branch analysis if not running on main
|
||||
if [ "$CI_COMMIT_BRANCH" != "main" ];then
|
||||
SCANNER_OPTS="$SCANNER_OPTS -Dsonar.pullrequest.branch=$CI_COMMIT_BRANCH -Dsonar.pullrequest.key=$CI_COMMIT_SHA -Dsonar.pullrequest.base=main"
|
||||
fi
|
||||
|
||||
# run the sonar-scanner
|
||||
eval "$SONAR_SCANNER_CMD $SCANNER_OPTS"
|
||||
|
||||
SONARQUBE_URL="https://sonarqube.corp.redhat.com/dashboard?id=osbuild%3Aimage-builder-frontend&pullRequest=$CI_COMMIT_SHA"
|
||||
# Report back to GitHub
|
||||
curl \
|
||||
-u "${SCHUTZBOT_LOGIN}" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/RedHatInsights/image-builder-frontend/statuses/${CI_COMMIT_SHA}" \
|
||||
-d '{"state":"success", "description": "SonarQube scan sent for analysis", "context": "SonarQube", "target_url": "'"${SONARQUBE_URL}"'"}'
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue