Compare commits
12 Commits
module-app
...
cli/fix/in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be38a6866f | ||
|
|
2181adb67c | ||
|
|
e1b0bd7875 | ||
|
|
e465a2d8fc | ||
|
|
70d0ae1ff5 | ||
|
|
e52db36045 | ||
|
|
1935b1fbb6 | ||
|
|
5dc69bf80e | ||
|
|
e839d5ae41 | ||
|
|
e89c6f35cc | ||
|
|
c4a7c81777 | ||
|
|
a69b5d40a9 |
@@ -317,7 +317,7 @@ spec:
|
||||
chown -R 1000:1000 /uploadstemp && \
|
||||
chown -R 1000:1000 /appdata
|
||||
- name: olares-app-init
|
||||
image: beclab/system-frontend:v1.6.28
|
||||
image: beclab/system-frontend:v1.6.30
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
@@ -1356,6 +1356,17 @@ data:
|
||||
proxy_set_header Connection '$connection_upgrade';
|
||||
more_set_headers 'Upgrade: $http_upgrade';
|
||||
}
|
||||
location /api/refresh {
|
||||
add_header Access-Control-Allow-Headers "access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,content-type,x-auth,x-unauth-error,x-authorization";
|
||||
add_header Access-Control-Allow-Methods "PUT, GET, DELETE, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Origin $http_origin;
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
proxy_pass http://authelia-backend-svc:9091;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-real-ip $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://headscale-server-svc:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: wizard
|
||||
image: beclab/wizard:v1.6.5
|
||||
image: beclab/wizard:v1.6.30
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
201
apps/README.md
201
apps/README.md
@@ -17,4 +17,203 @@ This directory contains the code for system applications, primarily for LarePass
|
||||
| Settings | A system configuration application. |
|
||||
| Dashboard | An app for monitoring system resource usage. |
|
||||
| Control Hub | The console for Olares, providing precise and autonomous control over the system and its environment. |
|
||||
| DevBox | A development tool for building and deploying Olares applications. |
|
||||
| Studio | A development tool for building and deploying Olares applications. |
|
||||
|
||||
|
||||
# Local Development Guide
|
||||
|
||||
This document describes how to start and develop various sub-projects locally.
|
||||
|
||||
## Available Projects
|
||||
|
||||
| Project | Command | Port |
|
||||
|---------|---------|------|
|
||||
| Desktop | `npm run dev:desktop` | 1090 |
|
||||
| Files | `npm run dev:files` | 5090 |
|
||||
| Settings | `npm run dev:settings` | 9000 |
|
||||
| Market | `npm run dev:market` | 8080 |
|
||||
| Vault | `npm run dev:vault` | 8090 |
|
||||
| Wise | `npm run dev:wise` | 8100 |
|
||||
| Dashboard | `npm run dev:dashboard` | 9003 |
|
||||
| Control Hub | `npm run dev:hub` | 9002 |
|
||||
| Share | `npm run dev:share` | 5070 |
|
||||
| Editor | `npm run dev:editor` | 9100 |
|
||||
| Preview | `npm run dev:preview` | 9001 |
|
||||
| Studio | `npm run dev:studio` | 9001 |
|
||||
|
||||
## Step 1: Modify Local Hosts File
|
||||
|
||||
Projects require access through a specific domain name. You need to configure the local hosts file first.
|
||||
|
||||
### macOS / Linux
|
||||
|
||||
1. Open terminal and edit the hosts file with administrator privileges:
|
||||
|
||||
```bash
|
||||
sudo vim /etc/hosts
|
||||
```
|
||||
|
||||
Or use the nano editor:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/hosts
|
||||
```
|
||||
|
||||
2. Add the following content at the end of the file:
|
||||
|
||||
```
|
||||
127.0.0.1 test.xxx.olares.com
|
||||
```
|
||||
|
||||
3. Save the file and exit
|
||||
- vim: Press `ESC`, type `:wq` and press Enter
|
||||
- nano: Press `Ctrl + O` to save, `Ctrl + X` to exit
|
||||
|
||||
### Windows
|
||||
|
||||
1. Run Notepad as administrator:
|
||||
- Search for "Notepad" in the Start menu
|
||||
- Right-click on "Notepad" and select "Run as administrator"
|
||||
|
||||
2. Open the hosts file in Notepad:
|
||||
- Click `File` -> `Open`
|
||||
- Paste the path in the filename field: `C:\Windows\System32\drivers\etc\hosts`
|
||||
- Change file type to "All Files (*.*)"
|
||||
- Click "Open"
|
||||
|
||||
3. Add the following content at the end of the file:
|
||||
|
||||
```
|
||||
127.0.0.1 test.xxx.olares.com
|
||||
```
|
||||
|
||||
4. Save the file (`Ctrl + S`)
|
||||
|
||||
5. Flush DNS cache (optional):
|
||||
- Open Command Prompt (CMD) as administrator
|
||||
- Run the following command:
|
||||
|
||||
```cmd
|
||||
ipconfig /flushdns
|
||||
```
|
||||
|
||||
## Step 2: Install Dependencies
|
||||
|
||||
Run in the project root directory (`olares-app`):
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Step 3: Configure Environment Variables
|
||||
|
||||
Create or edit the `.env` file in the `packages/app` directory and add the following content:
|
||||
|
||||
```env
|
||||
ACCOUNT_DOMAIN=xxx.olares.com
|
||||
DEV_DOMAIN=test.xxx.olares.com
|
||||
```
|
||||
|
||||
> **Note**:
|
||||
> - `ACCOUNT_DOMAIN`: Your Olares account domain, used for API proxy
|
||||
> - `DEV_DOMAIN`: Local development server domain, must match the domain configured in the hosts file
|
||||
|
||||
## Step 4: Start the Project
|
||||
|
||||
After configuring the `.env` file, run the corresponding command in the `packages/app` directory:
|
||||
|
||||
```bash
|
||||
# Start Desktop
|
||||
npm run dev:desktop
|
||||
|
||||
# Start Files
|
||||
npm run dev:files
|
||||
|
||||
# Start Settings
|
||||
npm run dev:settings
|
||||
|
||||
# Start Market
|
||||
npm run dev:market
|
||||
|
||||
# Start other projects...
|
||||
npm run dev:<project>
|
||||
```
|
||||
|
||||
## Step 5: Access the Application
|
||||
|
||||
After successful startup, visit in your browser (replace port according to the project):
|
||||
|
||||
| Project | URL |
|
||||
|---------|-----|
|
||||
| Desktop | `https://test.xxx.olares.com:1090` |
|
||||
| Files | `https://test.xxx.olares.com:5090` |
|
||||
| Settings | `https://test.xxx.olares.com:9000` |
|
||||
| Market | `https://test.xxx.olares.com:8080` |
|
||||
| Vault | `https://test.xxx.olares.com:8090` |
|
||||
| Wise | `https://test.xxx.olares.com:8100` |
|
||||
| Dashboard | `https://test.xxx.olares.com:9003` |
|
||||
| Control Hub | `https://test.xxx.olares.com:9002` |
|
||||
| Share | `https://test.xxx.olares.com:5070` |
|
||||
| Editor | `https://test.xxx.olares.com:9100` |
|
||||
| Preview | `https://test.xxx.olares.com:9001` |
|
||||
| Studio | `https://test.xxx.olares.com:9001` |
|
||||
|
||||
> **Note**: Since a self-signed certificate is used, the browser may display an insecure connection warning. Click "Advanced" and select "Proceed" to continue.
|
||||
|
||||
## Environment Variables (.env file)
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `ACCOUNT_DOMAIN` | Account domain (for API proxy) | `xxx.olares.com` |
|
||||
| `DEV_DOMAIN` | Development server domain | `test.xxx.olares.com` |
|
||||
|
||||
## FAQ
|
||||
|
||||
### 1. Cannot Access the Application
|
||||
|
||||
- Check if the hosts file is configured correctly
|
||||
- Ensure the development server has started successfully
|
||||
- Check if the firewall is blocking the corresponding port
|
||||
|
||||
### 2. Certificate Error
|
||||
|
||||
The development server uses HTTPS. The browser will show a certificate warning on first visit - this is expected behavior.
|
||||
|
||||
### 3. API Request Failed
|
||||
|
||||
Ensure the `ACCOUNT_DOMAIN` in the `.env` file is set correctly. The proxy configuration relies on this variable to forward requests to the correct backend service.
|
||||
|
||||
## Build for Production
|
||||
|
||||
```bash
|
||||
# Build Desktop
|
||||
npm run build:desktop
|
||||
|
||||
# Build Files
|
||||
npm run build:files
|
||||
|
||||
# Build Settings
|
||||
npm run build:settings
|
||||
|
||||
# Build other projects...
|
||||
npm run build:<project>
|
||||
```
|
||||
|
||||
### Build Output Directory
|
||||
|
||||
| Project | Output Directory |
|
||||
|---------|------------------|
|
||||
| Desktop | `dist/apps/desktop` |
|
||||
| Files | `dist/apps/files` |
|
||||
| Settings | `dist/apps/settings` |
|
||||
| Market | `dist/apps/market` |
|
||||
| Vault | `dist/apps/vault` |
|
||||
| Dashboard | `dist/apps/dashboard` |
|
||||
| Control Hub | `dist/apps/control-hub` |
|
||||
| Share | `dist/apps/share` |
|
||||
| Editor | `dist/apps/editor` |
|
||||
| Preview | `dist/apps/preview` |
|
||||
| **Wise** | `dist/spa` |
|
||||
| **Studio** | `dist/spa` |
|
||||
|
||||
> **Note**: Build outputs for Wise and Studio are located in `dist/spa` directory, not under `dist/apps/`.
|
||||
@@ -1,2 +1,2 @@
|
||||
DEV_DOMAIN=test.guotest334.olares.cn
|
||||
ACCOUNT_DOMAIN=guotest334.olares.cn
|
||||
DEV_DOMAIN=test.xxx.olares.com
|
||||
ACCOUNT_DOMAIN=xxx.olares.com
|
||||
|
||||
@@ -36,6 +36,10 @@ fi
|
||||
|
||||
|
||||
find $BASE_DIR/../ -type f -name Olares.yaml | while read f; do
|
||||
if [[ "$f" == *"/vendor/"* ]]; then
|
||||
echo "skip vendor file $f"
|
||||
continue
|
||||
fi
|
||||
echo "Processing $f"
|
||||
declare -a bins
|
||||
IFS=
|
||||
|
||||
@@ -86,6 +86,14 @@ func IsDir(path string) bool {
|
||||
return s.IsDir()
|
||||
}
|
||||
|
||||
func IsExecutable(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.Mode().Perm()&0111 != 0
|
||||
}
|
||||
|
||||
func CountDirFiles(dirName string) int {
|
||||
if !IsDir(dirName) {
|
||||
return 0
|
||||
|
||||
@@ -3,7 +3,10 @@ package upgrade
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -222,6 +225,71 @@ type upgradeGPUDriverIfNeeded struct {
|
||||
common.KubeAction
|
||||
}
|
||||
|
||||
// fixProcModprobePath fixes the /proc/sys/kernel/modprobe path issue that can cause
|
||||
// nvidia-installer to fail with error:
|
||||
// "The path to the `modprobe` utility reported by '/proc/sys/kernel/modprobe', ”, differs from
|
||||
// the path determined by `nvidia-installer`, `/bin/kmod`, and does not appear to point to a
|
||||
// valid `modprobe` binary."
|
||||
//
|
||||
// This function checks if /proc/sys/kernel/modprobe is empty or invalid, and if so,
|
||||
// writes a valid modprobe path to it.
|
||||
func fixProcModprobePath() {
|
||||
const procModprobePath = "/proc/sys/kernel/modprobe"
|
||||
|
||||
modprobePaths := []string{
|
||||
"/sbin/modprobe",
|
||||
"/usr/sbin/modprobe",
|
||||
"/bin/modprobe",
|
||||
"/usr/bin/modprobe",
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(procModprobePath)
|
||||
if err != nil {
|
||||
logger.Warnf("failed to read %s: %v", procModprobePath, err)
|
||||
}
|
||||
currentPath := strings.TrimSpace(string(data))
|
||||
|
||||
// Check if current path is valid (non-empty and executable)
|
||||
if currentPath != "" {
|
||||
if util.IsExecutable(currentPath) {
|
||||
logger.Debugf("%s already contains valid path: %s", procModprobePath, currentPath)
|
||||
return
|
||||
}
|
||||
// in case it's a symlink that resolves to a valid executable
|
||||
if resolved, err := filepath.EvalSymlinks(currentPath); err == nil && resolved != "" {
|
||||
if util.IsExecutable(resolved) {
|
||||
logger.Debugf("%s contains symlink %s -> %s which is valid", procModprobePath, currentPath, resolved)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger.Warnf("%s contains invalid path: '%s', attempting to fix", procModprobePath, currentPath)
|
||||
} else {
|
||||
logger.Warnf("%s is empty, attempting to fix", procModprobePath)
|
||||
}
|
||||
|
||||
if lookPath, err := exec.LookPath("modprobe"); err == nil && lookPath != "" {
|
||||
modprobePaths = append([]string{lookPath}, modprobePaths...)
|
||||
}
|
||||
|
||||
for _, modprobePath := range modprobePaths {
|
||||
if !util.IsExecutable(modprobePath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.WriteFile(procModprobePath, []byte(modprobePath), 0644); err != nil {
|
||||
logger.Warnf("failed to write %s to %s: %v", modprobePath, procModprobePath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Infof("successfully fixed %s: set to %s", procModprobePath, modprobePath)
|
||||
return
|
||||
}
|
||||
|
||||
// If we get here, we couldn't fix it, but we log a warning and continue
|
||||
// The nvidia-installer might still work, or it might fail, but we don't want to block the upgrade
|
||||
logger.Warnf("could not fix %s, nvidia-installer may fail; continuing anyway", procModprobePath)
|
||||
}
|
||||
|
||||
func (a *upgradeGPUDriverIfNeeded) Execute(runtime connector.Runtime) error {
|
||||
sys := runtime.GetSystemInfo()
|
||||
if sys.IsWsl() {
|
||||
@@ -296,6 +364,9 @@ func (a *upgradeGPUDriverIfNeeded) Execute(runtime connector.Runtime) error {
|
||||
if _, err := runtime.GetRunner().SudoCmd("DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends dkms build-essential linux-headers-$(uname -r)", false, true); err != nil {
|
||||
return errors.Wrap(errors.WithStack(err), "failed to install kernel build dependencies for NVIDIA runfile")
|
||||
}
|
||||
|
||||
fixProcModprobePath()
|
||||
|
||||
// install runfile
|
||||
runfile := item.FilePath(runtime.GetBaseDir())
|
||||
if _, err := runtime.GetRunner().SudoCmd(fmt.Sprintf("chmod +x %s", runfile), false, true); err != nil {
|
||||
|
||||
@@ -43,16 +43,18 @@ func (h *Handlers) getMountedPath(ctx *fiber.Ctx, mutate func(*disk.UsageStat) *
|
||||
u = mutate(u)
|
||||
}
|
||||
|
||||
res = append(res, &mountedPath{
|
||||
*u,
|
||||
string(p.Type),
|
||||
p.Invalid,
|
||||
p.IDSerial,
|
||||
p.IDSerialShort,
|
||||
p.PartitionUUID,
|
||||
p.Device,
|
||||
p.ReadOnly,
|
||||
})
|
||||
if u != nil {
|
||||
res = append(res, &mountedPath{
|
||||
*u,
|
||||
string(p.Type),
|
||||
p.Invalid,
|
||||
p.IDSerial,
|
||||
p.IDSerialShort,
|
||||
p.PartitionUUID,
|
||||
p.Device,
|
||||
p.ReadOnly,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return h.OkJSON(ctx, "success", res)
|
||||
@@ -64,7 +66,14 @@ func (h *Handlers) GetMountedPath(ctx *fiber.Ctx) error {
|
||||
|
||||
func (h *Handlers) GetMountedPathInCluster(ctx *fiber.Ctx) error {
|
||||
return h.getMountedPath(ctx, func(us *disk.UsageStat) *disk.UsageStat {
|
||||
us.Path = nodePathToClusterPath(us.Path)
|
||||
path := nodePathToClusterPath(us.Path)
|
||||
if path == us.Path {
|
||||
// not in cluster path
|
||||
return nil
|
||||
}
|
||||
|
||||
us.Path = path
|
||||
|
||||
return us
|
||||
})
|
||||
}
|
||||
|
||||
@@ -267,7 +267,8 @@ func MountUsbDevice(ctx context.Context, mountBaseDir string, dev []storageDevic
|
||||
if err = mounter.Mount(d.DevPath, mkMountDir, "", []string{"uid=1000", "gid=1000"}); err != nil {
|
||||
klog.Warning("mount usb error, ", err, ", ", d.DevPath, ", ", mkMountDir)
|
||||
// clear the empty mount dir
|
||||
if err = os.RemoveAll(mkMountDir); err != nil {
|
||||
// do not use remove all, only remove the mount point path, assume it's an empty dir
|
||||
if err = os.Remove(mkMountDir); err != nil {
|
||||
klog.Error("remove the mount dir error, ", err)
|
||||
}
|
||||
|
||||
@@ -287,7 +288,8 @@ func umountAndRemovePath(ctx context.Context, path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(path); err != nil {
|
||||
// do not use remove all, only remove the mount point path, assume it's an empty dir
|
||||
if err = os.Remove(path); err != nil {
|
||||
klog.Error("remove mount point error, ", err)
|
||||
}
|
||||
|
||||
@@ -397,6 +399,11 @@ func UmountBrokenMount(ctx context.Context, baseDir string) error {
|
||||
|
||||
klog.Infof("broken mountpoint: %v, %v, %v", m.Path, m.Device, r.Reason)
|
||||
|
||||
if err = umountAndRemovePath(ctx, m.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !isDeviceExists(m.Device) {
|
||||
klog.Infof("device not exists mountpoint: %v, %v", m.Path, m.Device)
|
||||
if err = umountAndRemovePath(ctx, m.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -659,3 +666,16 @@ func checkMount(mountPoint string, timeout time.Duration) result {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func isDeviceExists(devicePath string) bool {
|
||||
if !strings.HasPrefix(devicePath, "/dev") {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(devicePath, "/dev/mapper/") {
|
||||
return true
|
||||
}
|
||||
|
||||
_, err := os.Stat(devicePath)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ spec:
|
||||
priorityClassName: "system-cluster-critical"
|
||||
containers:
|
||||
- name: app-service
|
||||
image: beclab/app-service:0.4.67
|
||||
image: beclab/app-service:0.4.69
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
@@ -228,6 +228,8 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: OLARESD_HOST
|
||||
value: $(HOSTIP):18088
|
||||
volumeMounts:
|
||||
- mountPath: /charts
|
||||
name: charts-store
|
||||
|
||||
@@ -376,6 +376,14 @@ func (r *ApplicationReconciler) createApplication(ctx context.Context, req ctrl.
|
||||
|
||||
func (r *ApplicationReconciler) updateApplication(ctx context.Context, req ctrl.Request,
|
||||
deployment client.Object, app *appv1alpha1.Application, name string) error {
|
||||
// Skip update if triggered by app modification (not deployment change)
|
||||
if app.Annotations != nil {
|
||||
if lastVersion := app.Annotations[deploymentResourceVersionAnnotation]; lastVersion == deployment.GetResourceVersion() {
|
||||
klog.Infof("skip updateApplication: deployment %s not changed, triggered by app modification", deployment.GetName())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
appCopy := app.DeepCopy()
|
||||
appNames := getAppName(deployment)
|
||||
isMultiApp := len(appNames) > 1
|
||||
@@ -406,7 +414,7 @@ func (r *ApplicationReconciler) updateApplication(ctx context.Context, req ctrl.
|
||||
} else {
|
||||
appid = appv1alpha1.AppName(name).GetAppID()
|
||||
}
|
||||
_, sharedEntrances := r.getAppSettings(ctx, name, appid, owner, deployment, isMultiApp, entrancesMap[name])
|
||||
settings, sharedEntrances := r.getAppSettings(ctx, name, appid, owner, deployment, isMultiApp, entrancesMap[name])
|
||||
|
||||
appCopy.Spec.Name = name
|
||||
appCopy.Spec.Namespace = deployment.GetNamespace()
|
||||
@@ -415,7 +423,21 @@ func (r *ApplicationReconciler) updateApplication(ctx context.Context, req ctrl.
|
||||
appCopy.Spec.Icon = icon
|
||||
appCopy.Spec.SharedEntrances = sharedEntrances
|
||||
appCopy.Spec.Ports = servicePortsMap[name]
|
||||
appCopy.Spec.Entrances = entrancesMap[name]
|
||||
|
||||
// Merge entrances: preserve authLevel from existing, update other fields
|
||||
appCopy.Spec.Entrances = mergeEntrances(app.Spec.Entrances, entrancesMap[name])
|
||||
|
||||
if appCopy.Spec.Settings == nil {
|
||||
appCopy.Spec.Settings = make(map[string]string)
|
||||
}
|
||||
if settings["defaultThirdLevelDomainConfig"] != "" {
|
||||
appCopy.Spec.Settings["defaultThirdLevelDomainConfig"] = settings["defaultThirdLevelDomainConfig"]
|
||||
}
|
||||
|
||||
if incomingPolicy := settings[applicationSettingsPolicyKey]; incomingPolicy != "" {
|
||||
existingPolicy := appCopy.Spec.Settings[applicationSettingsPolicyKey]
|
||||
appCopy.Spec.Settings[applicationSettingsPolicyKey] = mergePolicySettings(existingPolicy, incomingPolicy)
|
||||
}
|
||||
|
||||
if tailScale != nil {
|
||||
appCopy.Spec.TailScale = *tailScale
|
||||
@@ -436,6 +458,13 @@ func (r *ApplicationReconciler) updateApplication(ctx context.Context, req ctrl.
|
||||
}
|
||||
}
|
||||
|
||||
// Record deployment resourceVersion to detect app-only modifications
|
||||
if appCopy.Annotations == nil {
|
||||
appCopy.Annotations = make(map[string]string)
|
||||
}
|
||||
klog.Infof("deploymentname: %s, version: %v", deployment.GetName(), deployment.GetResourceVersion())
|
||||
appCopy.Annotations[deploymentResourceVersionAnnotation] = deployment.GetResourceVersion()
|
||||
|
||||
err = r.Patch(ctx, appCopy, client.MergeFrom(app))
|
||||
if err != nil {
|
||||
klog.Infof("update spec failed %v", err)
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
appv1alpha1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationSettingsPolicyKey = "policy"
|
||||
namespaceFinalizer = "finalizers.bytetrade.io/namespaces"
|
||||
userFinalizer = "finalizers.bytetrade.io/users"
|
||||
creator = "bytetrade.io/creator"
|
||||
applicationSettingsPolicyKey = "policy"
|
||||
namespaceFinalizer = "finalizers.bytetrade.io/namespaces"
|
||||
userFinalizer = "finalizers.bytetrade.io/users"
|
||||
creator = "bytetrade.io/creator"
|
||||
deploymentResourceVersionAnnotation = "bytetrade.io/deployment-resource-version"
|
||||
)
|
||||
|
||||
type applicationSettingsSubPolicy struct {
|
||||
@@ -20,3 +27,59 @@ type applicationSettingsPolicy struct {
|
||||
OneTime bool `json:"one_time"`
|
||||
Duration int32 `json:"valid_duration"`
|
||||
}
|
||||
|
||||
// mergeEntrances merges new entrances with existing ones.
|
||||
// Preserves authLevel from existing entrances, other fields are updated from new entrances.
|
||||
func mergeEntrances(existing, incoming []appv1alpha1.Entrance) []appv1alpha1.Entrance {
|
||||
if len(existing) == 0 {
|
||||
return incoming
|
||||
}
|
||||
|
||||
existingByName := make(map[string]*appv1alpha1.Entrance, len(existing))
|
||||
for i := range existing {
|
||||
existingByName[existing[i].Name] = &existing[i]
|
||||
}
|
||||
|
||||
merged := make([]appv1alpha1.Entrance, 0, len(incoming))
|
||||
for _, entry := range incoming {
|
||||
if old, exists := existingByName[entry.Name]; exists {
|
||||
entry.AuthLevel = old.AuthLevel
|
||||
}
|
||||
merged = append(merged, entry)
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
func mergePolicySettings(existingPolicy, incomingPolicy string) string {
|
||||
if incomingPolicy == "" {
|
||||
return existingPolicy
|
||||
}
|
||||
if existingPolicy == "" {
|
||||
return incomingPolicy
|
||||
}
|
||||
|
||||
var existing, incoming map[string]applicationSettingsPolicy
|
||||
if err := json.Unmarshal([]byte(existingPolicy), &existing); err != nil {
|
||||
return incomingPolicy
|
||||
}
|
||||
if err := json.Unmarshal([]byte(incomingPolicy), &incoming); err != nil {
|
||||
return existingPolicy
|
||||
}
|
||||
|
||||
merged := make(map[string]applicationSettingsPolicy, len(incoming))
|
||||
for name, incomingEntry := range incoming {
|
||||
if existingEntry, exists := existing[name]; exists {
|
||||
incomingEntry.DefaultPolicy = existingEntry.DefaultPolicy
|
||||
incomingEntry.SubPolicies = existingEntry.SubPolicies
|
||||
}
|
||||
merged[name] = incomingEntry
|
||||
}
|
||||
|
||||
result, err := json.Marshal(merged)
|
||||
if err != nil {
|
||||
return existingPolicy
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
||||
303
framework/app-service/controllers/types_test.go
Normal file
303
framework/app-service/controllers/types_test.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
appv1alpha1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("mergeEntrances", func() {
|
||||
It("should return incoming when existing is empty", func() {
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 8080, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(nil, incoming)
|
||||
|
||||
Expect(result).To(Equal(incoming))
|
||||
})
|
||||
|
||||
It("should return incoming when existing is empty slice", func() {
|
||||
existing := []appv1alpha1.Entrance{}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 8080, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(Equal(incoming))
|
||||
})
|
||||
|
||||
It("should preserve authLevel from existing entrances", func() {
|
||||
existing := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "old-svc", Port: 80, AuthLevel: "private"},
|
||||
}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "new-svc", Port: 8080, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(HaveLen(1))
|
||||
Expect(result[0].Name).To(Equal("web"))
|
||||
Expect(result[0].Host).To(Equal("new-svc"))
|
||||
Expect(result[0].Port).To(Equal(int32(8080)))
|
||||
Expect(result[0].AuthLevel).To(Equal("private")) // preserved from existing
|
||||
})
|
||||
|
||||
It("should update other fields from incoming", func() {
|
||||
existing := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "old-svc", Port: 80, AuthLevel: "private", Title: "Old Title", Icon: "old-icon"},
|
||||
}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "new-svc", Port: 8080, AuthLevel: "public", Title: "New Title", Icon: "new-icon"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(HaveLen(1))
|
||||
Expect(result[0].Host).To(Equal("new-svc"))
|
||||
Expect(result[0].Port).To(Equal(int32(8080)))
|
||||
Expect(result[0].Title).To(Equal("New Title"))
|
||||
Expect(result[0].Icon).To(Equal("new-icon"))
|
||||
Expect(result[0].AuthLevel).To(Equal("private")) // preserved
|
||||
})
|
||||
|
||||
It("should handle new entrance not in existing", func() {
|
||||
existing := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 80, AuthLevel: "private"},
|
||||
}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 80, AuthLevel: "public"},
|
||||
{Name: "api", Host: "api-svc", Port: 3000, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(HaveLen(2))
|
||||
// web entrance preserves authLevel
|
||||
Expect(result[0].Name).To(Equal("web"))
|
||||
Expect(result[0].AuthLevel).To(Equal("private"))
|
||||
// api entrance uses incoming authLevel (no existing)
|
||||
Expect(result[1].Name).To(Equal("api"))
|
||||
Expect(result[1].AuthLevel).To(Equal("public"))
|
||||
})
|
||||
|
||||
It("should handle removed entrance from existing", func() {
|
||||
existing := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 80, AuthLevel: "private"},
|
||||
{Name: "api", Host: "api-svc", Port: 3000, AuthLevel: "private"},
|
||||
}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 80, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(HaveLen(1))
|
||||
Expect(result[0].Name).To(Equal("web"))
|
||||
Expect(result[0].AuthLevel).To(Equal("private"))
|
||||
})
|
||||
|
||||
It("should handle multiple entrances correctly", func() {
|
||||
existing := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-svc", Port: 80, AuthLevel: "private"},
|
||||
{Name: "admin", Host: "admin-svc", Port: 9000, AuthLevel: "internal"},
|
||||
}
|
||||
incoming := []appv1alpha1.Entrance{
|
||||
{Name: "web", Host: "web-new", Port: 8080, AuthLevel: "public"},
|
||||
{Name: "admin", Host: "admin-new", Port: 9090, AuthLevel: "public"},
|
||||
{Name: "api", Host: "api-svc", Port: 3000, AuthLevel: "public"},
|
||||
}
|
||||
|
||||
result := mergeEntrances(existing, incoming)
|
||||
|
||||
Expect(result).To(HaveLen(3))
|
||||
// web: authLevel preserved
|
||||
Expect(result[0].Name).To(Equal("web"))
|
||||
Expect(result[0].Host).To(Equal("web-new"))
|
||||
Expect(result[0].AuthLevel).To(Equal("private"))
|
||||
// admin: authLevel preserved
|
||||
Expect(result[1].Name).To(Equal("admin"))
|
||||
Expect(result[1].Host).To(Equal("admin-new"))
|
||||
Expect(result[1].AuthLevel).To(Equal("internal"))
|
||||
// api: new entrance, uses incoming authLevel
|
||||
Expect(result[2].Name).To(Equal("api"))
|
||||
Expect(result[2].AuthLevel).To(Equal("public"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("mergePolicySettings", func() {
|
||||
It("should return existing when incoming is empty", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, "")
|
||||
|
||||
Expect(result).To(Equal(existing))
|
||||
})
|
||||
|
||||
It("should return incoming when existing is empty", func() {
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","one_time":false,"sub_policies":[{"one_time":true,"policy":"one_factor","uri":"/api/send","valid_duration":0}],"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings("", incoming)
|
||||
|
||||
Expect(result).To(Equal(incoming))
|
||||
})
|
||||
|
||||
It("should preserve default_policy from existing", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"private","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultPolicy["calibreweb-svc"].DefaultPolicy).To(Equal("private"))
|
||||
})
|
||||
|
||||
It("should preserve sub_policies from existing", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"system","sub_policies":[{"uri":"/api/send","policy":"one_factor","one_time":true,"valid_duration":0}],"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":[{"uri":"/api/new","policy":"two_factor","one_time":false,"valid_duration":3600}],"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// sub_policies preserved from existing
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies[0].URI).To(Equal("/api/send"))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies[0].Policy).To(Equal("one_factor"))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies[0].OneTime).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should preserve both default_policy and sub_policies from existing", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"public","sub_policies":[{"uri":"/api/send","policy":"one_factor","one_time":true,"valid_duration":0}],"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":[{"uri":"/api/new","policy":"two_factor","one_time":false,"valid_duration":3600}],"one_time":true,"valid_duration":1800}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// default_policy preserved from existing
|
||||
Expect(resultPolicy["calibreweb-svc"].DefaultPolicy).To(Equal("public"))
|
||||
// sub_policies preserved from existing
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies[0].URI).To(Equal("/api/send"))
|
||||
// other fields use incoming
|
||||
Expect(resultPolicy["calibreweb-svc"].OneTime).To(BeTrue())
|
||||
Expect(resultPolicy["calibreweb-svc"].Duration).To(Equal(int32(1800)))
|
||||
})
|
||||
|
||||
It("should preserve null sub_policies from existing", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":[{"uri":"/api/send","policy":"one_factor","one_time":true,"valid_duration":0}],"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// sub_policies preserved as null from existing
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies).To(BeNil())
|
||||
})
|
||||
|
||||
It("should add new entrance policy with incoming values", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"private","sub_policies":[{"uri":"/old","policy":"one_factor","one_time":false,"valid_duration":0}],"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0},"api-svc":{"default_policy":"public","sub_policies":[{"uri":"/api","policy":"two_factor","one_time":true,"valid_duration":3600}],"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// existing entry: preserved default_policy and sub_policies
|
||||
Expect(resultPolicy["calibreweb-svc"].DefaultPolicy).To(Equal("private"))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["calibreweb-svc"].SubPolicies[0].URI).To(Equal("/old"))
|
||||
// new entry: uses incoming values
|
||||
Expect(resultPolicy).To(HaveKey("api-svc"))
|
||||
Expect(resultPolicy["api-svc"].DefaultPolicy).To(Equal("public"))
|
||||
Expect(resultPolicy["api-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["api-svc"].SubPolicies[0].URI).To(Equal("/api"))
|
||||
})
|
||||
|
||||
It("should delete entrance policy not in incoming", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0},"api-svc":{"default_policy":"public","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resultPolicy).To(HaveKey("calibreweb-svc"))
|
||||
Expect(resultPolicy).NotTo(HaveKey("api-svc"))
|
||||
})
|
||||
|
||||
It("should handle add, preserve, and delete together", func() {
|
||||
existing := `{
|
||||
"web-svc":{"default_policy":"private","sub_policies":[{"uri":"/web","policy":"one_factor","one_time":false,"valid_duration":0}],"one_time":false,"valid_duration":0},
|
||||
"admin-svc":{"default_policy":"internal","sub_policies":[{"uri":"/admin","policy":"two_factor","one_time":true,"valid_duration":3600}],"one_time":false,"valid_duration":0},
|
||||
"legacy-svc":{"default_policy":"public","sub_policies":null,"one_time":false,"valid_duration":0}
|
||||
}`
|
||||
incoming := `{
|
||||
"web-svc":{"default_policy":"system","sub_policies":null,"one_time":true,"valid_duration":1800},
|
||||
"admin-svc":{"default_policy":"system","sub_policies":[{"uri":"/new","policy":"one_factor","one_time":false,"valid_duration":0}],"one_time":false,"valid_duration":0},
|
||||
"api-svc":{"default_policy":"system","sub_policies":[{"uri":"/api","policy":"one_factor","one_time":false,"valid_duration":0}],"one_time":false,"valid_duration":0}
|
||||
}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
var resultPolicy map[string]applicationSettingsPolicy
|
||||
err := json.Unmarshal([]byte(result), &resultPolicy)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// web-svc: default_policy and sub_policies preserved, other fields from incoming
|
||||
Expect(resultPolicy["web-svc"].DefaultPolicy).To(Equal("private"))
|
||||
Expect(resultPolicy["web-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["web-svc"].SubPolicies[0].URI).To(Equal("/web"))
|
||||
Expect(resultPolicy["web-svc"].OneTime).To(BeTrue())
|
||||
Expect(resultPolicy["web-svc"].Duration).To(Equal(int32(1800)))
|
||||
// admin-svc: default_policy and sub_policies preserved
|
||||
Expect(resultPolicy["admin-svc"].DefaultPolicy).To(Equal("internal"))
|
||||
Expect(resultPolicy["admin-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["admin-svc"].SubPolicies[0].URI).To(Equal("/admin"))
|
||||
Expect(resultPolicy["admin-svc"].SubPolicies[0].Policy).To(Equal("two_factor"))
|
||||
// api-svc: new entry, uses incoming values
|
||||
Expect(resultPolicy).To(HaveKey("api-svc"))
|
||||
Expect(resultPolicy["api-svc"].DefaultPolicy).To(Equal("system"))
|
||||
Expect(resultPolicy["api-svc"].SubPolicies).To(HaveLen(1))
|
||||
Expect(resultPolicy["api-svc"].SubPolicies[0].URI).To(Equal("/api"))
|
||||
// legacy-svc: deleted (not in incoming)
|
||||
Expect(resultPolicy).NotTo(HaveKey("legacy-svc"))
|
||||
})
|
||||
|
||||
It("should return incoming when existing JSON is invalid", func() {
|
||||
existing := `invalid json`
|
||||
incoming := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
Expect(result).To(Equal(incoming))
|
||||
})
|
||||
|
||||
It("should return existing when incoming JSON is invalid", func() {
|
||||
existing := `{"calibreweb-svc":{"default_policy":"system","sub_policies":null,"one_time":false,"valid_duration":0}}`
|
||||
incoming := `invalid json`
|
||||
|
||||
result := mergePolicySettings(existing, incoming)
|
||||
|
||||
Expect(result).To(Equal(existing))
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
|
||||
"bytetrade.io/web3os/app-service/pkg/appcfg"
|
||||
@@ -258,6 +258,13 @@ func (h *HelmOps) SetValues() (values map[string]interface{}, err error) {
|
||||
|
||||
klog.Infof("values[node]: %#v", values["nodes"])
|
||||
|
||||
deviceName, err := utils.GetDeviceName()
|
||||
if err != nil {
|
||||
klog.Errorf("failed to get deviceName %v", err)
|
||||
return values, err
|
||||
}
|
||||
|
||||
values["deviceName"] = deviceName
|
||||
return values, err
|
||||
}
|
||||
|
||||
@@ -296,7 +303,7 @@ func (h *HelmOps) getInstalledApps(ctx context.Context) (installed bool, app []*
|
||||
func (h *HelmOps) AddEnvironmentVariables(values map[string]interface{}) error {
|
||||
values[constants.OlaresEnvHelmValuesKey] = make(map[string]interface{})
|
||||
appEnv, err := h.client.AppClient.SysV1alpha1().AppEnvs(h.app.Namespace).Get(h.ctx, apputils.FormatAppEnvName(h.app.AppName, h.app.OwnerName), metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -181,6 +181,14 @@ func (p *DownloadingApp) exec(ctx context.Context) error {
|
||||
}
|
||||
values["nodes"] = nodeInfo
|
||||
|
||||
deviceName, err := utils.GetDeviceName()
|
||||
if err != nil {
|
||||
klog.Errorf("failed to get deviceName %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
values["deviceName"] = deviceName
|
||||
|
||||
refs, err := p.getRefsForImageManager(appConfig, values)
|
||||
if err != nil {
|
||||
klog.Errorf("get image refs from resources failed %v", err)
|
||||
|
||||
@@ -235,6 +235,14 @@ func (p *UpgradingApp) exec(ctx context.Context) error {
|
||||
}
|
||||
values["nodes"] = nodeInfo
|
||||
|
||||
deviceName, err := utils.GetDeviceName()
|
||||
if err != nil {
|
||||
klog.Errorf("failed to get deviceName %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
values["deviceName"] = deviceName
|
||||
|
||||
refs, err := p.getRefsForImageManager(appConfig, values)
|
||||
if err != nil {
|
||||
klog.Errorf("get image refs from resources failed %v", err)
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -399,3 +402,69 @@ func GetNodeInfo(ctx context.Context) (ret []api.NodeInfo, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type SystemStatusResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
TerminusdState string `json:"terminusdState"`
|
||||
TerminusState string `json:"terminusState"`
|
||||
TerminusName string `json:"terminusName"`
|
||||
TerminusVersion string `json:"terminusVersion"`
|
||||
InstalledTime int64 `json:"installedTime"`
|
||||
InitializedTime int64 `json:"initializedTime"`
|
||||
OlaresdVersion string `json:"olaresdVersion"`
|
||||
DeviceName string `json:"device_name"`
|
||||
HostName string `json:"host_name"`
|
||||
OsType string `json:"os_type"`
|
||||
OsArch string `json:"os_arch"`
|
||||
OsInfo string `json:"os_info"`
|
||||
OsVersion string `json:"os_version"`
|
||||
CpuInfo string `json:"cpu_info"`
|
||||
GpuInfo string `json:"gpu_info"`
|
||||
Memory string `json:"memory"`
|
||||
Disk string `json:"disk"`
|
||||
WifiConnected bool `json:"wifiConnected"`
|
||||
WiredConnected bool `json:"wiredConnected"`
|
||||
HostIp string `json:"hostIp"`
|
||||
ExternalIp string `json:"externalIp"`
|
||||
InstallingState string `json:"installingState"`
|
||||
InstallingProgress string `json:"installingProgress"`
|
||||
UninstallingState string `json:"uninstallingState"`
|
||||
UninstallingProgress string `json:"uninstallingProgress"`
|
||||
UpgradingTarget string `json:"upgradingTarget"`
|
||||
UpgradingRetryNum int `json:"upgradingRetryNum"`
|
||||
UpgradingState string `json:"upgradingState"`
|
||||
UpgradingStep string `json:"upgradingStep"`
|
||||
UpgradingProgress string `json:"upgradingProgress"`
|
||||
UpgradingError string `json:"upgradingError"`
|
||||
UpgradingDownloadState string `json:"upgradingDownloadState"`
|
||||
UpgradingDownloadStep string `json:"upgradingDownloadStep"`
|
||||
UpgradingDownloadProgress string `json:"upgradingDownloadProgress"`
|
||||
UpgradingDownloadError string `json:"upgradingDownloadError"`
|
||||
CollectingLogsState string `json:"collectingLogsState"`
|
||||
CollectingLogsError string `json:"collectingLogsError"`
|
||||
DefaultFrpServer string `json:"defaultFrpServer"`
|
||||
FrpEnable string `json:"frpEnable"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func GetDeviceName() (string, error) {
|
||||
url := fmt.Sprintf("http://%s/system/status", os.Getenv("OLARESD_HOST"))
|
||||
var result SystemStatusResponse
|
||||
client := resty.New()
|
||||
resp, err := client.R().SetResult(&result).Get(url)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to send request to olaresd %v", err)
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
klog.Errorf("failed to get system status from olaresd %v", err)
|
||||
return "", errors.New(string(resp.Body()))
|
||||
}
|
||||
if result.Code != http.StatusOK {
|
||||
return "", fmt.Errorf("not exepcted result code: %v,message: %v", result.Code, result.Message)
|
||||
}
|
||||
klog.Infof("getDeviceName: %#v", result.Data)
|
||||
return result.Data.DeviceName, nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
name: check-auth
|
||||
containers:
|
||||
- name: auth-front
|
||||
image: beclab/login:v1.6.26
|
||||
image: beclab/login:v1.6.30
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
||||
@@ -266,7 +266,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: api
|
||||
image: beclab/bfl:v0.4.36
|
||||
image: beclab/bfl:v0.4.37
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
|
||||
@@ -243,11 +243,34 @@ func (h *Handler) handleActivate(req *restful.Request, resp *restful.Response) {
|
||||
return
|
||||
}
|
||||
|
||||
// for owner user, use the reverse proxy config from the payload
|
||||
// for non-owner user, reuse the owner's reverse proxy config
|
||||
isOwner := userOp.GetUserAnnotation(user, constants.UserAnnotationOwnerRole) == constants.RoleOwner
|
||||
|
||||
reverseProxyConf := &ReverseProxyConfig{}
|
||||
if payload.FRP.Host != "" {
|
||||
reverseProxyConf.EnableFRP = true
|
||||
reverseProxyConf.FRPServer = payload.FRP.Host
|
||||
reverseProxyConf.FRPAuthMethod = FRPAuthMethodJWS
|
||||
if isOwner {
|
||||
if payload.FRP.Host != "" {
|
||||
reverseProxyConf.EnableFRP = true
|
||||
reverseProxyConf.FRPServer = payload.FRP.Host
|
||||
reverseProxyConf.FRPAuthMethod = FRPAuthMethodJWS
|
||||
}
|
||||
} else {
|
||||
ownerUser, ownerErr := userOp.GetOwnerUser()
|
||||
if ownerErr != nil {
|
||||
err = fmt.Errorf("activate system: failed to get owner user for reverse proxy config: %v", ownerErr)
|
||||
klog.Error(err)
|
||||
response.HandleError(resp, err)
|
||||
return
|
||||
}
|
||||
ownerNamespace := fmt.Sprintf(constants.UserspaceNameFormat, ownerUser.Name)
|
||||
ownerConf, ownerConfErr := GetReverseProxyConfigFromNamespace(ctx, ownerNamespace)
|
||||
if ownerConfErr != nil {
|
||||
err = fmt.Errorf("activate system: failed to get owner's reverse proxy config: %v", ownerConfErr)
|
||||
klog.Error(err)
|
||||
response.HandleError(resp, err)
|
||||
return
|
||||
}
|
||||
reverseProxyConf = ownerConf
|
||||
}
|
||||
// no matter whether the reverse proxy is enabled, we need to persist the configuration
|
||||
// so that the configuration can be fetched by the frontend
|
||||
|
||||
@@ -549,7 +549,11 @@ func (conf *ReverseProxyConfig) generateReverseProxyConfigMapData() map[string]s
|
||||
}
|
||||
|
||||
func GetReverseProxyConfig(ctx context.Context) (*ReverseProxyConfig, error) {
|
||||
configData, err := k8sutil.GetConfigMapData(ctx, constants.Namespace, constants.ReverseProxyConfigMapName)
|
||||
return GetReverseProxyConfigFromNamespace(ctx, constants.Namespace)
|
||||
}
|
||||
|
||||
func GetReverseProxyConfigFromNamespace(ctx context.Context, namespace string) (*ReverseProxyConfig, error) {
|
||||
configData, err := k8sutil.GetConfigMapData(ctx, namespace, constants.ReverseProxyConfigMapName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error getting configmap")
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ spec:
|
||||
command:
|
||||
- /samba_share
|
||||
- name: files
|
||||
image: beclab/files-server:v0.2.140
|
||||
image: beclab/files-server:v0.2.141
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
|
||||
@@ -140,7 +140,7 @@ spec:
|
||||
name: check-chart-repo
|
||||
containers:
|
||||
- name: appstore-backend
|
||||
image: beclab/market-backend:v0.6.10
|
||||
image: beclab/market-backend:v0.6.12
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 81
|
||||
|
||||
@@ -240,7 +240,7 @@ spec:
|
||||
value: os_framework_search3
|
||||
containers:
|
||||
- name: search3
|
||||
image: beclab/search3:v0.0.96
|
||||
image: beclab/search3:v0.0.97
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -301,7 +301,7 @@ spec:
|
||||
priorityClassName: "system-cluster-critical"
|
||||
containers:
|
||||
- name: search3monitor
|
||||
image: beclab/search3monitor:v0.0.96
|
||||
image: beclab/search3monitor:v0.0.97
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
|
||||
@@ -64,7 +64,7 @@ spec:
|
||||
operator: Exists
|
||||
containers:
|
||||
- name: search3-validation
|
||||
image: beclab/search3validation:v0.0.96
|
||||
image: beclab/search3validation:v0.0.97
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
|
||||
@@ -252,6 +252,7 @@ data:
|
||||
not startswith(container.image, "ghcr.io/coder/coder:v2.19.0")
|
||||
not startswith(container.image, "apecloud/")
|
||||
not startswith(container.image, "docker.io/apecloud/")
|
||||
not startswith(container.image, "kldtks/")
|
||||
}
|
||||
|
||||
is_root_user(ctx) if {
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/gofiber/contrib/websocket v1.2.2
|
||||
github.com/gofiber/fiber/v2 v2.49.2
|
||||
github.com/gofiber/fiber/v2 v2.52.9
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
@@ -50,7 +50,7 @@ require (
|
||||
require (
|
||||
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||
github.com/Azure/azure-storage-blob-go v0.15.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.50.8 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
@@ -93,7 +93,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go v6.0.14+incompatible // indirect
|
||||
@@ -123,7 +123,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
|
||||
@@ -40,8 +40,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/apecloud/kubeblocks v1.0.0 h1:ditcH7RNZyDiWiHP1V7IuXWHdcBJv3/X5N89Qlz+xew=
|
||||
github.com/apecloud/kubeblocks v1.0.0/go.mod h1:Mk5xRLm2MpxoTNZKEdDcrIY3I1EpokQBU3Q9Zwse8MI=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
@@ -221,8 +221,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/contrib/websocket v1.2.2 h1:6lygrypMM0LqfPUC8N5MZ5apsU9/3K/NJULrIVpS8FU=
|
||||
github.com/gofiber/contrib/websocket v1.2.2/go.mod h1:QPOQ5qazfR/oz7FZD4p5PO9B8TaxjAnaUG/xpbFI1r4=
|
||||
github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs=
|
||||
github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
@@ -393,8 +393,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@@ -598,8 +598,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
|
||||
Reference in New Issue
Block a user