Skip to content

Commit f5612dd

Browse files
committed
feat: improve IDE setup with SIP-aware permission handling
- Add proactive permission checking for VSCode product.json editing - Provide clear SIP protection error messages and manual setup instructions - Improve JetBrains setup log message formatting for better readability - Add comprehensive IDE setup documentation This enables VSCode and JetBrains IDE configuration to work properly on macOS systems with System Integrity Protection (SIP) enabled.
1 parent 1575c10 commit f5612dd

File tree

3 files changed

+451
-156
lines changed

3 files changed

+451
-156
lines changed

general/ide/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# JFrog CLI IDE Configuration
2+
3+
Automates JFrog Artifactory repository configuration for IDEs.
4+
5+
## Features
6+
7+
- **VSCode**: Configures extension gallery to use JFrog Artifactory
8+
- **JetBrains**: Configures plugin repositories for all JetBrains IDEs
9+
- **Cross-platform**: Windows, macOS, Linux support
10+
- **Automatic backup**: Creates backups before modifications
11+
- **Auto-detection**: Finds IDE installations automatically
12+
13+
## Commands
14+
15+
### VSCode
16+
```bash
17+
# Configure VSCode extensions repository
18+
jf vscode config --repo=<REPO_KEY> --artifactory-url=<URL>
19+
20+
# Install VSCode extension from repository
21+
jf vscode install --publisher=<PUBLISHER> --extension-name=<EXTENSION_NAME> --repo=<REPO_KEY> [--version=<VERSION>] [--artifactory-url=<URL>]
22+
23+
# Examples
24+
jf vscode config --repo=vscode-extensions --artifactory-url=https://myartifactory.com/
25+
sudo jf vscode config --repo=vscode-extensions --artifactory-url=https://myartifactory.com/ # macOS/Linux
26+
27+
# Install specific extension
28+
jf vscode install --publisher=microsoft --extension-name=vscode-python --repo=vscode-extensions
29+
jf vscode install --publisher=ms-python --extension-name=python --version=2023.12.0 --repo=vscode-extensions
30+
```
31+
32+
### JetBrains
33+
```bash
34+
# Configure JetBrains plugin repository
35+
jf jetbrains config --repo=<REPO_KEY> --artifactory-url=<URL>
36+
37+
# Examples
38+
jf jetbrains config --repo=jetbrains-plugins --artifactory-url=https://myartifactory.com/
39+
```
40+
41+
## How It Works
42+
43+
### VSCode
44+
**Configuration:**
45+
1. Detects VSCode installation (`product.json` location)
46+
2. Creates backup in `~/.jfrog/backup/ide/vscode/`
47+
3. Modifies `extensionsGallery.serviceUrl` using:
48+
- **macOS/Linux**: `sed` command
49+
- **Windows**: PowerShell regex replacement
50+
4. Preserves all other VSCode configuration
51+
52+
**Extension Installation:**
53+
1. Validates extension exists in Artifactory repository
54+
2. Downloads extension package (.vsix) from JFrog Artifactory
55+
3. Installs extension using VSCode CLI (`code --install-extension`)
56+
4. Supports specific version installation or latest version
57+
5. Cross-platform support (Windows/macOS/Linux)
58+
59+
### JetBrains
60+
1. Scans for JetBrains IDE configurations
61+
2. Creates backups in `~/.jfrog/backup/ide/jetbrains/`
62+
3. Updates `idea.properties` files with repository URLs
63+
4. Supports all JetBrains IDEs (IntelliJ, PyCharm, WebStorm, etc.)
64+
65+
## Platform Requirements
66+
67+
- **macOS**: Requires `sudo` for system-installed IDEs
68+
- **Windows**: Requires "Run as Administrator"
69+
- **Linux**: Requires `sudo` for system-installed IDEs
70+
71+
## File Locations
72+
73+
### VSCode
74+
- **macOS**: `/Applications/Visual Studio Code.app/Contents/Resources/app/product.json`
75+
- **Windows**: `%LOCALAPPDATA%\Programs\Microsoft VS Code\resources\app\product.json`
76+
- **Linux**: `/usr/share/code/resources/app/product.json`
77+
78+
### JetBrains
79+
- **macOS**: `~/Library/Application Support/JetBrains/`
80+
- **Windows**: `%APPDATA%\JetBrains\`
81+
- **Linux**: `~/.config/JetBrains/`
82+
83+
## Backup & Recovery
84+
85+
- Automatic backups created before modifications
86+
- Located in `~/.jfrog/backup/ide/`
87+
- Automatic restore on failure
88+
- Manual restore: copy backup file back to original location

general/ide/jetbrains/jetbrains.go

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -61,29 +61,29 @@ func NewJetbrainsCommand(repoKey, artifactoryURL string) *JetbrainsCommand {
6161

6262
// Run executes the JetBrains configuration command
6363
func (jc *JetbrainsCommand) Run() error {
64-
log.Info("Configuring JetBrains IDEs to use JFrog Artifactory plugins repository...")
65-
66-
// Get the Artifactory URL if not provided
67-
if jc.artifactoryURL == "" {
68-
serverDetails, err := config.GetDefaultServerConf()
69-
if err != nil {
70-
return errorutils.CheckError(fmt.Errorf("failed to get default server configuration: %w", err))
71-
}
72-
if serverDetails == nil {
73-
return errorutils.CheckError(fmt.Errorf("no default server configuration found. Please configure JFrog CLI or provide --artifactory-url"))
64+
log.Info("Configuring JetBrains IDEs plugin repository...")
65+
66+
var repoURL string
67+
if jc.repoKey == "" {
68+
repoURL = jc.artifactoryURL
69+
} else {
70+
if jc.artifactoryURL == "" {
71+
serverDetails, err := config.GetDefaultServerConf()
72+
if err != nil {
73+
return errorutils.CheckError(fmt.Errorf("failed to get default server configuration: %w", err))
74+
}
75+
if serverDetails == nil {
76+
return errorutils.CheckError(fmt.Errorf("no default server configuration found. Please configure JFrog CLI or provide --artifactory-url"))
77+
}
78+
jc.artifactoryURL = serverDetails.GetUrl()
7479
}
75-
jc.artifactoryURL = serverDetails.GetUrl()
80+
repoURL = jc.buildRepositoryURL()
7681
}
7782

78-
// Build the complete repository URL
79-
repoURL := jc.buildRepositoryURL()
80-
81-
// Validate repository connection
8283
if err := jc.validateRepository(repoURL); err != nil {
8384
return errorutils.CheckError(fmt.Errorf("repository validation failed: %w", err))
8485
}
8586

86-
// Auto-detect JetBrains IDE installations
8787
if err := jc.detectJetBrainsIDEs(); err != nil {
8888
return errorutils.CheckError(fmt.Errorf("failed to detect JetBrains IDEs: %w\n\nManual setup instructions:\n%s", err, jc.getManualSetupInstructions(repoURL)))
8989
}
@@ -94,54 +94,50 @@ func (jc *JetbrainsCommand) Run() error {
9494

9595
log.Info(fmt.Sprintf("Found %d JetBrains IDE installation(s):", len(jc.detectedIDEs)))
9696
for _, ide := range jc.detectedIDEs {
97-
log.Info(fmt.Sprintf(" %s %s", ide.Name, ide.Version))
97+
log.Info(fmt.Sprintf(" %s %s", ide.Name, ide.Version))
9898
}
9999

100-
// Create backups and modify each IDE
101100
modifiedCount := 0
102101
for _, ide := range jc.detectedIDEs {
103-
log.Info(fmt.Sprintf("\nConfiguring %s %s...", ide.Name, ide.Version))
102+
log.Info(fmt.Sprintf("Configuring %s %s...", ide.Name, ide.Version))
104103

105-
// Create backup
106104
if err := jc.createBackup(ide); err != nil {
107105
log.Warn(fmt.Sprintf("Failed to create backup for %s: %v", ide.Name, err))
108106
continue
109107
}
110108

111-
// Modify properties file
112109
if err := jc.modifyPropertiesFile(ide, repoURL); err != nil {
113110
log.Error(fmt.Sprintf("Failed to configure %s: %v", ide.Name, err))
114-
// Attempt to restore backup
115111
if restoreErr := jc.restoreBackup(ide); restoreErr != nil {
116112
log.Error(fmt.Sprintf("Failed to restore backup for %s: %v", ide.Name, restoreErr))
117113
}
118114
continue
119115
}
120116

121117
modifiedCount++
122-
log.Info(fmt.Sprintf("%s %s configured successfully", ide.Name, ide.Version))
118+
log.Info(fmt.Sprintf("%s %s configured successfully", ide.Name, ide.Version))
123119
}
124120

125121
if modifiedCount == 0 {
126122
return errorutils.CheckError(fmt.Errorf("failed to configure any JetBrains IDEs\n\nManual setup instructions:\n%s", jc.getManualSetupInstructions(repoURL)))
127123
}
128124

129-
log.Info(fmt.Sprintf("\nSuccessfully configured %d out of %d JetBrains IDE(s)!", modifiedCount, len(jc.detectedIDEs)))
125+
log.Info(fmt.Sprintf("Successfully configured %d out of %d JetBrains IDE(s)", modifiedCount, len(jc.detectedIDEs)))
130126
log.Info("Repository URL:", repoURL)
131-
log.Info("\nPlease restart your JetBrains IDEs for the changes to take effect.")
127+
log.Info("Please restart your JetBrains IDEs to apply changes")
132128

133129
return nil
134130
}
135131

136132
// buildRepositoryURL constructs the complete repository URL
137133
func (jc *JetbrainsCommand) buildRepositoryURL() string {
138134
baseURL := strings.TrimSuffix(jc.artifactoryURL, "/")
139-
return fmt.Sprintf("%s/artifactory/api/jetbrains/%s", baseURL, jc.repoKey)
135+
return fmt.Sprintf("%s/artifactory/%s", baseURL, jc.repoKey)
140136
}
141137

142138
// validateRepository checks if the repository is accessible
143139
func (jc *JetbrainsCommand) validateRepository(repoURL string) error {
144-
log.Info("Validating repository connection...")
140+
log.Info("Validating repository...")
145141

146142
client := &http.Client{Timeout: 10 * time.Second}
147143
resp, err := client.Get(repoURL)
@@ -151,13 +147,13 @@ func (jc *JetbrainsCommand) validateRepository(repoURL string) error {
151147
defer resp.Body.Close()
152148

153149
if resp.StatusCode == 404 {
154-
return fmt.Errorf("repository not found (404). Please verify the repository key '%s' exists and has anonymous read access", jc.repoKey)
150+
return fmt.Errorf("repository not found (404). Please verify the repository key '%s' exists", jc.repoKey)
155151
}
156-
if resp.StatusCode >= 400 {
152+
if resp.StatusCode >= 400 && resp.StatusCode != 401 {
157153
return fmt.Errorf("repository returned status %d. Please verify the repository is accessible", resp.StatusCode)
158154
}
159155

160-
log.Info("Repository validation successful")
156+
log.Info("Repository validation successful")
161157
return nil
162158
}
163159

@@ -273,7 +269,7 @@ func (jc *JetbrainsCommand) createBackup(ide IDEInstallation) error {
273269
}
274270

275271
jc.backupPaths[ide.PropertiesPath] = backupPath
276-
log.Info(fmt.Sprintf("Backup created at: %s", backupPath))
272+
log.Info(fmt.Sprintf(" Backup created at: %s", backupPath))
277273
return nil
278274
}
279275

@@ -302,7 +298,7 @@ func (jc *JetbrainsCommand) restoreBackup(ide IDEInstallation) error {
302298
return fmt.Errorf("failed to restore backup: %w", err)
303299
}
304300

305-
log.Info(fmt.Sprintf("Backup restored for %s", ide.Name))
301+
log.Info(fmt.Sprintf(" Backup restored for %s", ide.Name))
306302
return nil
307303
}
308304

0 commit comments

Comments
 (0)