Sisense Community logo
    • Community Feedback
    • Chapters
    • Events
    • Forums
      • Help and How To
      • Product Feedback Forum
      • Strategy & Use Cases
    • Blogs
    • KB Docs
      • KB Docs
      • Add-Ons & Plug-Ins
      • APIs
      • Best Practices
      • Blox
      • CDT
      • Cloud Managed Service
      • Data Models
      • Data Sources
      • Embedding Analytics
      • How-Tos & FAQs
      • Onboarding
      • PySisense
      • Security
      • Sisense Administration
      • Sisense Intelligence & AI
      • Troubleshooting
      • Widget & Dashboard Scripts
    • Support
    • Learning
      • Sisense Academy: Free Courses and Certifications
      • Official Developer Documentation
      • Official Product Documentation
      • Official Sisense Youtube Channel
      • Sisense Compose SDK Playground
      • Official Sisense Discord
    • Use Case Gallery
    Discussions
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
    •                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    Discussions
    • TagsChevronRightIcon
    Deploy: Linux
    • Blog banner
      • PySisenseChevronRightIcon

      Managing Sisense Plugin States with PySisense

                                                                                                       

      Managing Sisense Plugin States with PySisense Overview Enabling and disabling Sisense plugins through the admin interface becomes time consuming when maintaining consistent plugin configurations across environments, testing against a clean instance, or iterating on plugin development. This article describes plugin_manager_pysisense.py , a Python command-line tool that manages Sisense plugin states using the PySisense library and a configuration file. The script is also a working example of how PySisense can be used for Sisense automation. This tool covers the same functionality as plugin_manager.py , documented in Managing Sisense Plugin States via the REST API . The difference is that this version delegates all API request code to PySisense rather than using the requests library directly. The two scripts share the same config.yaml format and produce identical output. The script enables and disables plugins, isolates a single plugin for faster development builds, and saves and restores named snapshots of plugin state. It does not install, uninstall, or update plugins, and does not roll back plugin versions. About PySisense PySisense is introduced in PySisense: Programmable Sisense Environment Management on the Sisense Community as a Python SDK for the Sisense REST API. It provides wrappers around common Sisense operations, including plugin management, dashboard access, and user administration. It is installed via pip and does not need to be placed alongside the script. This script provides a practical example of PySisense applied to a real automation task. Using PySisense rather than writing directly against the REST API reduces the amount of code required and removes several categories of boilerplate. Authentication headers, HTTP session management, SSL configuration, pagination across large result sets, and request logging are all handled by the library. The consistent SisenseClient and module pattern also makes it straightforward to extend a script to cover other Sisense operations, such as users, dashboards, or data models, without reworking connection and authentication code. Scripts built on PySisense are shorter and easier to read than equivalent scripts built on requests directly, and the library is maintained and tested independently of the scripts that use it. Note: The current release of PySisense on PyPI does not yet include the Plugins module used in this script. This functionality is planned for the next PySisense release. The development branch of the PySisense repository already contains it and can be installed directly from there. This script uses the Plugins class from PySisense: get_all_plugins() returns the full plugin list as a list of dicts, handling the pagination in the underlying API automatically. enable_plugins(names, bulk=True) enables one or more plugins by name. When bulk=True , all changes are sent in a single PATCH request. disable_plugins(names, bulk=True) disables one or more plugins by name, with the same bulk option. A SisenseClient instance is constructed from the config values and passed to the Plugins class. All HTTP calls are then handled by PySisense. from pysisense import Plugins, SisenseClient client = SisenseClient(domain="https://your-sisense-instance.com", token="YOUR_TOKEN") plugins = Plugins(api_client=client) # List all installed plugins all_plugins = plugins.get_all_plugins() # Enable plugins by short name, full name, or folderName result = plugins.enable_plugins(["SwitchDimension", "plugin-CustomPlugin"], bulk=True) # Disable plugins result = plugins.disable_plugins(["UnneededPlugin"], bulk=True) Both enable_plugins and disable_plugins return a summary dict with changed , already_enabled or already_disabled , not_found , and errors keys. Features that require knowing the current plugin state before making changes, such as strict mode, enable-one, and dry-run previews, call get_all_plugins() once at the start of the command and compute the set of changes at the script level. Plugin name matching Plugin names in the configuration file and on the command line accept either the short form ( SwitchDimension ) or the full form ( plugin-SwitchDimension ). Matching is case-insensitive. PySisense normalizes names internally, so both forms work with enable_plugins() and disable_plugins() as well. Prerequisites Python 3.10 or later Network access to the Sisense instance (the tool runs remotely and does not need to be installed on the server) A Sisense API token: Admin > REST API > v1.0 > GET /authentication/tokens/api Setup Install the required Python packages: pip install pysisense pyyaml Create a config.yaml and fill in the Sisense instance URL and API token: sisense: url: https://your-sisense-instance.com token: YOUR_API_TOKEN_HERE Add the plugins to enable to the enable_plugins list. See the Configuration reference section for all available settings. The script is then ready to run. Configuration reference sisense: url: https://your-sisense-instance.com token: YOUR_API_TOKEN_HERE # Admin → REST API → v1.0 → GET /authentication/tokens/api bulk: true # set false to update plugins one at a time instead of a single batch request # Plugins to enable. # Accepts short names (SwitchDimension) or full names (plugin-SwitchDimension). # Matching is case-insensitive. # # Default behavior: enable listed plugins; leave every other plugin unchanged. # With --strict: also disable any currently-enabled plugin NOT in this list, # making the instance exactly match this config. enable_plugins: - examplePlugin # Plugins to explicitly disable (optional). # Only needed in the default sync (no --strict), where unlisted plugins are # left alone. Listing a plugin here ensures it stays off and documents the # intentional exclusion. # disable_plugins: # - anotherExamplePlugin enable_plugins : the list of plugins to enable. By default, plugins not on this list are left in their current state. With --strict , any currently enabled plugin not on this list will be disabled. disable_plugins : optional. Plugins to explicitly disable in the default sync without --strict . Useful for documenting intentional exclusions when strict mode is not being used. bulk : controls whether plugin state changes are sent as a single batch API request or one at a time. Set to false for individual requests, which is slower but can be useful for diagnosing issues with specific plugins. Usage Run the script with no arguments to enable plugins in enable_plugins , disable plugins in disable_plugins , and leave everything else unchanged: python plugin_manager_pysisense.py Before making any changes, the script saves a timestamped snapshot of the current plugin state. To restore the most recent snapshot: python plugin_manager_pysisense.py --restore-snapshot To preview what would change without making any API calls: python plugin_manager_pysisense.py --dry-run Common use cases Maintaining a known good plugin baseline Organizations that need to ensure consistent plugin configuration across upgrades, support sessions, or environment changes can maintain an enable_plugins list in the configuration file. Running the script applies the defined configuration to the instance. To also disable any currently enabled plugin not in the list: python plugin_manager_pysisense.py --strict Testing on a stock Sisense instance Plugin interference is a common source of issues that are difficult to reproduce. To quickly disable all plugins and test against a clean instance, clear enable_plugins in the configuration file and run with --strict . The current state is saved automatically before any changes are made, and can be restored when testing is complete: # with enable_plugins cleared in config.yaml python plugin_manager_pysisense.py --strict # restore when done python plugin_manager_pysisense.py --restore-snapshot Developing and testing a single plugin When many plugins are installed, Sisense rebuilds all of them whenever any plugin changes. Using --enable-one disables all currently enabled plugins and enables only the specified plugin, which can significantly reduce rebuild time on instances with many plugins: python plugin_manager_pysisense.py --enable-one MyPlugin Snapshots The script saves snapshots as YAML files to a snapshots/ folder alongside the script. Snapshots capture the full enabled and disabled plugin state at a point in time. Snapshots are saved automatically before any run that changes state. Named snapshots can also be saved and restored explicitly: # save the current state with a specific name python plugin_manager_pysisense.py --save-snapshot before-upgrade # list all saved snapshots python plugin_manager_pysisense.py --list-snapshots # restore a specific snapshot python plugin_manager_pysisense.py --restore-snapshot before-upgrade Including --log-version when saving a snapshot records the version of each plugin at that point in time. Snapshots saved by plugin_manager_pysisense.py and plugin_manager.py are compatible. Both scripts read from the same snapshots/ directory, and each handles the other's snapshot format. Full option reference usage: plugin_manager_pysisense.py [-h] [--config FILE] [--dry-run] [--no-bulk] [--skip-snapshot] [--log-version] [--strict] [--enable-only | --disable-only] [--enable-one PLUGIN | --save-snapshot [NAME] | --list-snapshots | --restore-snapshot [NAME]] Manage Sisense plugin states via PySisense. options: -h, --help show this help message and exit --config FILE Path to config.yaml (default: config.yaml next to this script) --dry-run Preview what would change; make no API calls and save no snapshot --no-bulk Send one API call per plugin instead of a single batch PATCH. Slower but easier to diagnose partial failures. Overrides sisense.bulk in config. --skip-snapshot Do not automatically save a snapshot before this run --log-version Record each plugin's version field (from the API response) in any snapshot saved during this run --strict Also disable every currently enabled plugin that is not listed in enable_plugins. Without this flag, only plugins in disable_plugins are disabled; everything else is left alone. Always active for --restore-snapshot. --enable-only Run only the enable step; skip all disables --disable-only Run only the disable step; skip all enables --enable-one PLUGIN Enable exactly one plugin by name and disable all currently enabled others. Accepts the short or full plugin name. --save-snapshot [NAME] Save the current plugin state as a named snapshot. Omit NAME for an auto generated timestamp filename. Combine with --log-version to also record versions. --list-snapshots List all saved snapshots with creation date and enabled plugin count --restore-snapshot [NAME] Restore plugin state to exactly match a saved snapshot. Omit NAME to use the most recently created snapshot. Always strict (disables plugins not in snapshot) unless --enable-only. Plugin names accept the short form (SwitchDimension) or the full form (plugin-SwitchDimension) in both CLI args and config.yaml. Default behavior: enable plugins in enable_plugins, disable plugins in disable_plugins, leave everything else unchanged. Use --strict to also disable any enabled plugin not in enable_plugins. A snapshot is saved automatically before each run that modifies state. Use --skip-snapshot to suppress this. Use Case Managing plugins via PySisense and a configuration file offers several advantages over manual administration: Applies plugin active list consistently and repeatably without manual steps Provides automatic rollback through snapshots saved before each change Supports quickly switching between plugin states for testing or development Reduces the time required to restore a known working plugin list after an issue is resolved Demonstrates how PySisense can be used to build automation scripts for Sisense instances Summary With this tool in place, administrators and developers can manage Sisense plugin states quickly and reliably from the command line. The configuration file serves as a single source of truth for the intended plugin set, and the snapshot system ensures that any change can be reversed. The script also serves as a concrete example of how PySisense can be used in automation scripts: constructing a SisenseClient from config values, passing it to a feature class, and using the returned result dicts to drive output and error handling. The code is not compressed or obfuscated and can be modified as needed or used as a reference for similar tools. j""" plugin_manager_pysisense.py — Manage Sisense plugin states via pySisense. Same feature set as plugin_manager.py but delegates all API calls to the pySisense library instead of calling the Sisense REST API directly. Configuration is read from config.yaml. This script is also a working example of how pySisense can be used for Sisense automation. Install pySisense with: pip install pysisense Overview Manages Sisense plugin states via pySisense. Treats plugin configuration as code: define which plugins should be enabled in config.yaml, run the script, and the instance matches, quickly, from anywhere with network access. Before applying any changes, the current plugin state is automatically captured in a timestamped snapshot. If a change needs to be reverted, a single --restore-snapshot command rolls the instance back to exactly how it was. By default only the plugins explicitly listed in config.yaml are enabled or disabled, everything else is left as is. Use --strict to also disable any enabled plugin not in the list, making the instance enabled plugin list exactly match the config. Common use cases Maintain a known good plugin baseline Keep enable_plugins in config.yaml up to date. Run the script after upgrades, support sessions, or any time the instance may have drifted from the expected state. Test against a stock (no plugins) Sisense instance Clear enable_plugins in config.yaml, run --strict to disable everything, test, then restore: python plugin_manager_pysisense.py --strict python plugin_manager_pysisense.py --restore-snapshot Develop and iterate on a single plugin quickly python plugin_manager_pysisense.py --enable-one MyPlugin Roll back any change instantly python plugin_manager_pysisense.py --list-snapshots python plugin_manager_pysisense.py --restore-snapshot before-upgrade Usage # Enable listed plugins, disable listed disable_plugins, leave rest unchanged python plugin_manager_pysisense.py # Strict: also disables any enabled plugin not in enable_plugins python plugin_manager_pysisense.py --strict # Preview what would change (no API calls, no auto snapshot) python plugin_manager_pysisense.py --dry-run python plugin_manager_pysisense.py --strict --dry-run # Run only one half of the sync python plugin_manager_pysisense.py --enable-only # enable listed; skip all disables python plugin_manager_pysisense.py --disable-only # disable listed; skip all enables # Enable exactly one plugin and disable every other currently enabled plugin python plugin_manager_pysisense.py --enable-one SwitchDimension # Snapshots python plugin_manager_pysisense.py --save-snapshot [NAME] python plugin_manager_pysisense.py --save-snapshot [NAME] --log-version python plugin_manager_pysisense.py --list-snapshots python plugin_manager_pysisense.py --restore-snapshot [NAME] # omit NAME for most recent # Other flags (combinable with any of the above) --no-bulk one API call per plugin instead of a single batch request --skip-snapshot do not save an auto-snapshot before this run --log-version record each plugin's version in any snapshot saved this run --config FILE use a config file other than the default config.yaml Plugin names All plugin names accept either the short form (SwitchDimension) or the full form (plugin-SwitchDimension), both in CLI arguments and in config.yaml. Matching is case insensitive. API token Admin → REST API → v1.0 → GET /authentication/tokens/api Setup pip install pysisense pyyaml # Python 3.10+ required cp config.yaml.example config.yaml # Edit config.yaml: set sisense.url, sisense.token, and enable_plugins list """ import sys from datetime import datetime, timezone from pathlib import Path import yaml from pysisense import Plugins, SisenseClient SCRIPT_DIR = Path(__file__).parent DEFAULT_CONFIG = SCRIPT_DIR / "config.yaml" SNAPSHOTS_DIR = SCRIPT_DIR / "snapshots" # Config def load_config(path: Path) -> dict: if not path.exists(): print(f"Config file not found: {path}") print("Copy config.yaml.example to config.yaml and fill in your instance URL and API token.") sys.exit(1) with path.open() as f: return yaml.safe_load(f) def build_client(sisense_cfg: dict) -> SisenseClient: url = sisense_cfg.get("url", "") token = sisense_cfg.get("token", "") if not url or not token: print("config.yaml must include sisense.url and sisense.token.") sys.exit(1) is_ssl = not url.startswith("http://") return SisenseClient(domain=url, token=token, is_ssl=is_ssl) # Name matching — mirrors pySisense internals, used for dry-run previews and # strict-mode computation (pySisense handles matching internally for real calls) def _normalize(name: str) -> str: lower = name.lower() return lower[len("plugin-"):] if lower.startswith("plugin-") else lower def _build_lookup(names: list) -> set: lookup: set = set() for n in names: norm = _normalize(n) lookup.add(norm) lookup.add("plugin-" + norm) return lookup def _matches_lookup(plugin: dict, lookup: set) -> bool: return bool({plugin["folderName"].lower(), plugin["name"].lower()} & lookup) # Snapshot file I/O def write_snapshot_file(name: str, snapshot: dict) -> Path: SNAPSHOTS_DIR.mkdir(exist_ok=True) path = SNAPSHOTS_DIR / f"{name}.yaml" with path.open("w") as f: yaml.dump(snapshot, f, sort_keys=False, default_flow_style=False, allow_unicode=True) return path def read_snapshot_file(name: str) -> dict: path = SNAPSHOTS_DIR / f"{name}.yaml" if not path.exists(): print(f"Snapshot not found: {path}") sys.exit(1) with path.open() as f: data = yaml.safe_load(f) # Translate 'enable_plugins' key (plugin_manager.py snapshot format) to 'plugins' if "enable_plugins" in data and "plugins" not in data: data["plugins"] = data["enable_plugins"] return data def list_snapshot_files() -> list: if not SNAPSHOTS_DIR.exists(): return [] return sorted(SNAPSHOTS_DIR.glob("*.yaml")) def latest_snapshot_name() -> str: files = list_snapshot_files() if not files: print("No snapshots found.") sys.exit(1) def created_at(path: Path) -> str: with path.open() as f: return yaml.safe_load(f).get("created", "") return max(files, key=created_at).stem def _auto_save_snapshot(all_plugins: list, include_version: bool) -> None: name = datetime.now().strftime("auto_%Y%m%d_%H%M%S") print(f"\nSaving snapshot '{name}' before applying changes...") try: enabled = sorted(p["folderName"] for p in all_plugins if p.get("isEnabled")) disabled = sorted(p["folderName"] for p in all_plugins if not p.get("isEnabled")) snapshot: dict = { "plugins": enabled, "created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), } if include_version: versions: dict = {} for p in all_plugins: v = p.get("version") or p.get("pluginVersion") if v: versions[p["folderName"]] = str(v) if versions: snapshot["plugin_versions"] = versions path = write_snapshot_file(name, snapshot) print(f" Saved to {path} ({len(enabled)} enabled / {len(disabled)} disabled)") except Exception as exc: print(f" Warning: auto snapshot failed: {exc}") # Sync computation — determines which plugins need their state changed. # Used for dry-run previews, strict mode, and enable-one. def _compute_updates( all_plugins: list, enable_names: list, disable_names: list, *, strict: bool, enable_only: bool, disable_only: bool, ) -> tuple: enable_lookup = _build_lookup(enable_names) disable_lookup = _build_lookup(disable_names) matched_enable = [p for p in all_plugins if _matches_lookup(p, enable_lookup)] enable_folder_set = {p["folderName"] for p in matched_enable} to_enable: list = [] if disable_only else sorted( p["folderName"] for p in matched_enable if not p.get("isEnabled") ) if enable_only: to_disable: list = [] elif strict: to_disable = sorted( p["folderName"] for p in all_plugins if p.get("isEnabled") and p["folderName"] not in enable_folder_set ) else: to_disable = sorted( p["folderName"] for p in all_plugins if p.get("isEnabled") and _matches_lookup(p, disable_lookup) ) matched_norms = ( {_normalize(p["folderName"]) for p in matched_enable} | {_normalize(p["name"]) for p in matched_enable} ) unmatched = sorted(n for n in enable_names if _normalize(n) not in matched_norms) return to_enable, to_disable, unmatched # Output helpers def _print_update_plan( to_enable: list, to_disable: list, already_correct: int, untouched: int = 0, ) -> None: print(f" To enable: {len(to_enable)}") print(f" To disable: {len(to_disable)}") if already_correct > 0: print(f" Already set: {already_correct}") if untouched > 0: print(f" Untouched: {untouched} (not listed in config; left as-is)") print() def _print_dry_run_preview(to_enable: list, to_disable: list) -> None: if not to_enable and not to_disable: print("Nothing to change.") if to_enable: print("Would enable:") for f in to_enable: print(f" + {f}") if to_disable: print("Would disable:") for f in to_disable: print(f" - {f}") print("\n[DRY RUN] No changes made.") def _report_unmatched(unmatched: list) -> None: if unmatched: print(f"\nNot found in instance ({len(unmatched)}):") for n in unmatched: print(f" - {n}") def _apply_enable(plugins_obj: Plugins, to_enable: list, bulk: bool) -> int: if not to_enable: return 0 result = plugins_obj.enable_plugins(to_enable, bulk=bulk) if "error" in result: print(f"Failed to enable plugins: {result['error']}") sys.exit(1) for f in result["changed"]: print(f" [ENABLED] {f}") return len(result.get("errors", [])) def _apply_disable(plugins_obj: Plugins, to_disable: list, bulk: bool) -> int: if not to_disable: return 0 result = plugins_obj.disable_plugins(to_disable, bulk=bulk) if "error" in result: print(f"Failed to disable plugins: {result['error']}") sys.exit(1) for f in result["changed"]: print(f" [DISABLED] {f}") return len(result.get("errors", [])) # Commands def cmd_sync_to_config( plugins_obj: Plugins, enable_names: list, disable_names: list, *, dry_run: bool, bulk: bool, strict: bool, enable_only: bool, disable_only: bool, skip_snapshot: bool, include_version: bool, ) -> None: print("Fetching plugin list...") all_plugins = plugins_obj.get_all_plugins() if all_plugins and "error" in all_plugins[0]: print(f"Failed to fetch plugins: {all_plugins[0]['error']}") sys.exit(1) print(f" {len(all_plugins)} plugins total\n") to_enable, to_disable, unmatched = _compute_updates( all_plugins, enable_names, disable_names, strict=strict, enable_only=enable_only, disable_only=disable_only, ) enable_lookup = _build_lookup(enable_names) disable_lookup = _build_lookup(disable_names) matched = [p for p in all_plugins if _matches_lookup(p, enable_lookup)] already_correct = sum(1 for p in matched if p.get("isEnabled")) untouched = 0 if strict or enable_only else sum( 1 for p in all_plugins if not _matches_lookup(p, enable_lookup) and not _matches_lookup(p, disable_lookup) ) if matched: print(f"Matched {len(matched)} plugin(s) from enable_plugins:\n") print(f" {'Folder name':<45} {'API name':<35} {'Enabled'}") print(f" {'-'*45} {'-'*35} {'-'*7}") for p in sorted(matched, key=lambda x: x["folderName"]): print(f" {p['folderName']:<45} {p['name']:<35} {p['isEnabled']}") print() _print_update_plan(to_enable, to_disable, already_correct, untouched) if dry_run: _print_dry_run_preview(to_enable, to_disable) _report_unmatched(unmatched) return if not skip_snapshot and (to_enable or to_disable): _auto_save_snapshot(all_plugins, include_version) errors = _apply_disable(plugins_obj, to_disable, bulk) + _apply_enable(plugins_obj, to_enable, bulk) if not to_enable and not to_disable: print("Nothing to change.") else: print(f"\nDone. Enabled: {len(to_enable)} Disabled: {len(to_disable)} Errors: {errors}") _report_unmatched(unmatched) def cmd_enable_one( plugins_obj: Plugins, plugin_name: str, *, dry_run: bool, bulk: bool, skip_snapshot: bool, include_version: bool, ) -> None: target_lookup = _build_lookup([plugin_name]) print("Fetching plugin list...") all_plugins = plugins_obj.get_all_plugins() if all_plugins and "error" in all_plugins[0]: print(f"Failed to fetch plugins: {all_plugins[0]['error']}") sys.exit(1) print(f" {len(all_plugins)} plugins total\n") target = next((p for p in all_plugins if _matches_lookup(p, target_lookup)), None) if target is None: print(f"Plugin not found: {plugin_name!r}") print("Tip: check spelling against the Sisense admin panel, or run --save-snapshot to capture current names.") sys.exit(1) target_folder = target["folderName"] target_on = target.get("isEnabled", False) to_enable = [] if target_on else [target_folder] to_disable = sorted( p["folderName"] for p in all_plugins if p.get("isEnabled") and p["folderName"] != target_folder ) print(f"Target plugin: {target_folder}") print(f" Currently: {'enabled' if target_on else 'disabled'}\n") _print_update_plan(to_enable, to_disable, already_correct=1 if target_on else 0) if dry_run: _print_dry_run_preview(to_enable, to_disable) return if not skip_snapshot and (to_enable or to_disable): _auto_save_snapshot(all_plugins, include_version) errors = _apply_disable(plugins_obj, to_disable, bulk) + _apply_enable(plugins_obj, to_enable, bulk) if not to_enable and not to_disable: print("Nothing to change.") else: print(f"\nDone. Enabled: {len(to_enable)} Disabled: {len(to_disable)} Errors: {errors}") def cmd_save_named_snapshot(plugins_obj: Plugins, name: str, include_version: bool) -> None: print("Fetching plugin list...") all_plugins = plugins_obj.get_all_plugins() if all_plugins and "error" in all_plugins[0]: print(f"Failed to fetch plugins: {all_plugins[0]['error']}") sys.exit(1) print(f" {len(all_plugins)} plugins total\n") enabled = sorted(p["folderName"] for p in all_plugins if p.get("isEnabled")) disabled = sorted(p["folderName"] for p in all_plugins if not p.get("isEnabled")) snapshot: dict = { "plugins": enabled, "created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), } if include_version: versions: dict = {} for p in all_plugins: v = p.get("version") or p.get("pluginVersion") if v: versions[p["folderName"]] = str(v) if versions: snapshot["plugin_versions"] = versions path = write_snapshot_file(name, snapshot) print(f"Snapshot '{name}' saved to {path}") print(f" {len(enabled)} enabled / {len(disabled)} disabled plugin(s) captured.") if include_version and snapshot.get("plugin_versions"): print(f" Versions recorded for {len(snapshot['plugin_versions'])} plugin(s).") def cmd_list_snapshots() -> None: files = list_snapshot_files() if not files: print("No snapshots found.") return print(f"Snapshots ({len(files)}):\n") print(f" {'Name':<32} {'Created':<22} {'Enabled':<9} {'Versions'}") print(f" {'-'*32} {'-'*22} {'-'*9} {'-'*8}") for path in files: with path.open() as f: data = yaml.safe_load(f) enabled_count = len(data.get("plugins") or data.get("enable_plugins", [])) versions_logged = "yes" if data.get("plugin_versions") else "no" print( f" {path.stem:<32} " f"{data.get('created', 'unknown'):<22} " f"{enabled_count:<9} " f"{versions_logged}" ) def cmd_restore_snapshot( plugins_obj: Plugins, name: str, *, dry_run: bool, bulk: bool, enable_only: bool, disable_only: bool, skip_snapshot: bool, include_version: bool, ) -> None: snapshot = read_snapshot_file(name) snapshot_plugins = set(snapshot.get("plugins", [])) print(f"Snapshot '{name}' ({snapshot.get('created', 'unknown')})") print(f" {len(snapshot_plugins)} plugin(s) enabled in snapshot") if snapshot.get("plugin_versions"): print(f" Plugin versions recorded: {len(snapshot['plugin_versions'])}") print() print("Fetching plugin list...") all_plugins = plugins_obj.get_all_plugins() if all_plugins and "error" in all_plugins[0]: print(f"Failed to fetch plugins: {all_plugins[0]['error']}") sys.exit(1) print(f" {len(all_plugins)} plugins total\n") by_folder = {p["folderName"]: p for p in all_plugins} to_enable: list = [] if disable_only else sorted( f for f in snapshot_plugins if f in by_folder and not by_folder[f].get("isEnabled") ) to_disable: list = [] if enable_only else sorted( p["folderName"] for p in all_plugins if p.get("isEnabled") and p["folderName"] not in snapshot_plugins ) not_in_instance = sorted(f for f in snapshot_plugins if f not in by_folder) already_correct = max(0, len(snapshot_plugins) - len(to_enable) - len(not_in_instance)) _print_update_plan(to_enable, to_disable, already_correct) if dry_run: _print_dry_run_preview(to_enable, to_disable) if not_in_instance: print(f"\nNot found in instance ({len(not_in_instance)}):") for f in not_in_instance: print(f" - {f}") return if not skip_snapshot and (to_enable or to_disable): _auto_save_snapshot(all_plugins, include_version) errors = _apply_disable(plugins_obj, to_disable, bulk) + _apply_enable(plugins_obj, to_enable, bulk) if not to_enable and not to_disable: print("Nothing to change.") else: print(f"\nDone. Enabled: {len(to_enable)} Disabled: {len(to_disable)} Errors: {errors}") if not_in_instance: print(f"\nNot found in instance ({len(not_in_instance)}):") for f in not_in_instance: print(f" - {f}") # Main def main() -> None: import argparse parser = argparse.ArgumentParser( prog="plugin_manager_pysisense.py", description="Manage Sisense plugin states via pySisense.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "Plugin names accept the short form (SwitchDimension) or the full\n" "form (plugin-SwitchDimension) in both CLI args and config.yaml.\n" "\n" "Default behavior: enable plugins in enable_plugins, disable plugins\n" "in disable_plugins, leave everything else unchanged.\n" "Use --strict to also disable any enabled plugin not in enable_plugins.\n" "\n" "A snapshot is saved automatically before each run that\n" "modifies state. Use --skip-snapshot to suppress this." ), ) parser.add_argument( "--config", default=str(DEFAULT_CONFIG), metavar="FILE", help="Path to config.yaml (default: config.yaml next to this script)", ) parser.add_argument( "--dry-run", action="store_true", help="Preview what would change; make no API calls and save no snapshot", ) parser.add_argument( "--no-bulk", action="store_true", help=( "Send one API call per plugin instead of a single batch PATCH. " "Slower but easier to diagnose partial failures. " "Overrides sisense.bulk in config." ), ) parser.add_argument( "--skip-snapshot", action="store_true", help="Do not automatically save a snapshot before this run", ) parser.add_argument( "--log-version", action="store_true", help=( "Record each plugin's version field (from the API response) in any " "snapshot saved during this run (manual --save-snapshot or auto saved snapshot)" ), ) parser.add_argument( "--strict", action="store_true", help=( "Also disable every currently enabled plugin that is not listed in " "enable_plugins. Without this flag, only plugins in disable_plugins are " "disabled; everything else is left alone. " "Always active for --restore-snapshot." ), ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( "--enable-only", action="store_true", help="Run only the enable step; skip all disables", ) direction_group.add_argument( "--disable-only", action="store_true", help="Run only the disable step; skip all enables", ) mode_group = parser.add_mutually_exclusive_group() mode_group.add_argument( "--enable-one", metavar="PLUGIN", help=( "Enable exactly one plugin by name and disable all currently enabled " "others. Accepts the short or full plugin name." ), ) mode_group.add_argument( "--save-snapshot", metavar="NAME", nargs="?", const="__auto__", help=( "Save the current plugin state as a named snapshot. " "Omit NAME for an auto generated timestamp filename. " "Combine with --log-version to also record versions for each plugin." ), ) mode_group.add_argument( "--list-snapshots", action="store_true", help="List all saved snapshots with creation date and enabled plugin count", ) mode_group.add_argument( "--restore-snapshot", metavar="NAME", nargs="?", const="__latest__", help=( "Restore plugin state to exactly match a saved snapshot. " "Omit NAME to use the most recently created snapshot. " "Always strict (disables plugins not in snapshot) unless --enable-only." ), ) args = parser.parse_args() if args.list_snapshots: cmd_list_snapshots() return cfg = load_config(Path(args.config)) sisense_cfg = cfg.get("sisense", {}) bulk = sisense_cfg.get("bulk", True) and not args.no_bulk client = build_client(sisense_cfg) plugins_obj = Plugins(api_client=client) if args.enable_one: cmd_enable_one( plugins_obj, args.enable_one, dry_run=args.dry_run, bulk=bulk, skip_snapshot=args.skip_snapshot, include_version=args.log_version, ) elif args.save_snapshot is not None: name = ( datetime.now().strftime("snapshot_%Y%m%d_%H%M%S") if args.save_snapshot == "__auto__" else args.save_snapshot ) cmd_save_named_snapshot(plugins_obj, name, args.log_version) elif args.restore_snapshot is not None: name = ( latest_snapshot_name() if args.restore_snapshot == "__latest__" else args.restore_snapshot ) cmd_restore_snapshot( plugins_obj, name, dry_run=args.dry_run, bulk=bulk, enable_only=args.enable_only, disable_only=args.disable_only, skip_snapshot=args.skip_snapshot, include_version=args.log_version, ) else: enable_names = cfg.get("enable_plugins") or cfg.get("plugins", []) disable_names = cfg.get("disable_plugins", []) if not enable_names and not disable_names: print("config.yaml must include at least one entry in 'enable_plugins'.") sys.exit(1) cmd_sync_to_config( plugins_obj, enable_names, disable_names, dry_run=args.dry_run, bulk=bulk, strict=args.strict, enable_only=args.enable_only, disable_only=args.disable_only, skip_snapshot=args.skip_snapshot, include_version=args.log_version, ) if __name__ == "__main__": main() Disclaimer: This post outlines a potential custom workaround for a specific use case or provides instructions regarding a specific task. The solution may not work in all scenarios or Sisense versions, so we strongly recommend testing it in your environment before deployment. If you need further assistance with this, please let us know.

      Jeremy Friedel
      Jeremy FriedelPosted 1 month ago • Last reply 1 month ago
      1
               
    • Blog banner
      • News & UpdatesChevronRightIcon

      New academy content for administrators!

                                                                                       

      New academy content for administrators! We are pleased to announce the launch of 45 ALL-NEW COURSES that are AVAILABLE NOW for Administrators! These courses will help Admins of all skill levels by expanding their knowledge, providing hands-on opportunities, and covering all of our LATEST features and best practices (approximately 6 to 7 hours of content and hands-on practice). Our new courses are based on brand-new data and assets, divided into microlearning units. They are interactive and accessible! ( yes, yes, we finally have captions! ) To get access to the new Administrators learning path, CLICK HERE and then click on the blue Get Started button to register. If you are new to the Sisense Academy, I encourage you to make an account and sign up for courses based on your role. This is just the beginning of new content releases in Sisense Academy as we are working hard behind the scenes on the next set, and we look forward to sharing more with you soon! This is a continuation of the Academy Content Refresh. During January 2025, we launched 25 ALL-NEW COURSES that are AVAILABLE NOW for Data Designers, and during March 2025, we launched 15 ALL-NEW COURSES that are AVAILABLE NOW for Dashboards Designers. I hope to see you all in the Academy!

      iyyar_sg
      iyyar_sgPosted 1 year ago
      0
               
    • Blog banner
      • Widget & Dashboard ScriptsChevronRightIcon

      Customizing the Offset Size of Selected Categories in Sisense Pie Chart Widgets

                                                                                               

      Customizing the Offset Size of Selected Categories in Sisense Pie Chart Widgets In Sisense, pie chart widgets, including the various pie chart style options such as donut charts, are a common way to visualize data. By default, when a user selects a category within a pie chart, that slice "pops out" to highlight the selection. This article explains how to customize the offset size of selected categories in Sisense pie chart widgets by leveraging the  Highcharts settings of the widget using Sisense widget scripting capabilities. Understanding the Default Behavior When a slice in a Sisense pie chart is selected, it moves outward from the center by a medium-sized offset. The size of this move is set by the slicedOffset property in the Highcharts setting object , Highcharts is the JavaScript visualizing library that Sisense uses for rendering pie charts and many other common widget types.   Default Pie Chat Offset Default Donut Pie Chart Offset The Highcharts slicedOffset Property The slicedOffset property determines the distance, in pixels, that a slice is moved when it is selected or "sliced out." By modifying this property, it is possible to increase or decrease the offset to better fit the visualization requirements. The unit is always set in raw pixels as an integer and not any other unit. Accessing and Editing the Highcharts Options Object via Sisense Widget Scripting To customize the Highcharts settings object within a Sisense widget, the beforeviewloaded event in a widget script can be used. This specific widget event is triggered immediately before Highcharts is used to begin rendering the already fetched and processed data from the Sisense data source, allowing customization of the chart options before the chart is displayed. Implementation Steps   Add a Widget Script Navigate to the pie chart widget in question. Open the widget's script editor to add a custom script. Insert the Custom Script Use the following code as a template:     // Increase or decrease the size of the offset when a slice in the pie/donut chart is selected widget.on('beforeviewloaded', function (widget, env) { // Pixels to move the selected slice when selected var slicedOffset = 50; // Set the desired offset size here // Access Highcharts options for the widget var chartOptions = env.options; // Access pie chart specific Highcharts options var pieOptions = chartOptions.plotOptions.pie; // Set the offset of the selected slice pieOptions.slicedOffset = slicedOffset; });       Customize the Offset Value Modify the slicedOffset variable to the desired number of pixels. For example, to decrease the offset size, it might be set to 3, to increase it might be set to 50. Trial and error can be used to determine the best value for the use case.   Offset Increased to 50   Offset Increased to 50 in Donut Pie Chart   Save and Refresh Save the script and refresh the dashboard. The pie chart will now reflect the new offset size when a slice is selected. Important Considerations   Highcharts Version Compatibility Sisense uses a specific version of Highcharts that may differ between Sisense versions and might not always be the latest available version of Highcharts. To ensure compatibility:   Check the Highcharts Version : In the browser's console , enter Highcharts.version to display the current Highcharts version used by Sisense.   Consult Appropriate Documentation : Use the version number to refer to the correct Highcharts documentation in case any settings have changed between versions. Viewing Current Highcharts Settings For advanced customizations, it can be helpful to view the existing explicitly defined by Sisense Highcharts options for a widget:     widget.on('beforeviewloaded', function (widget, env) { console.log('Highchart Options: ', env.options); // Logs the current Highcharts options to the console });     By printing the env.options paramater, other customizable properties within the Highcharts configuration object can be viewed. Not all possible or relevant Highchart options are by default defined by Sisense when the default value is not being modified, for example, the slicedOffset parameter is not set directly by Sisense, it did not exist in the default Sisense Highchart Setting object before being defined in a script, the Highchart documentation contains the full list of possible Highcharts settings.   Extending Customizations The scripting customization method described here isn't limited to adjusting the slicedOffset property. Any Highcharts setting available for the chart type by altering the env.options object within the beforeviewloaded event. This allows extensive visual customization of Sisense widgets. Conclusion Customizing the offset size of selected slices in Sisense pie chart widgets can enhances the interactivity and readability of pie chart visualizations. By tapping into the Highcharts settings through the beforeviewloaded event, it is possible to have granular control over the chart's behavior and appearance. Other Highcharts settings can also be modified using this script template. References Highcharts Documentation : Highcharts Reference Sisense Documentation on Widget Scripts : Sisense Widget JavaScript Sisense Academy course on Widget Plugins and Scripts: Advanced Dashboards with Plug-ins and Scripts

      Jeremy Friedel
      Jeremy FriedelPosted 1 year ago
      0
               
      • TroubleshootingChevronRightIcon

      Troubleshooting Pods in Kubernetes Clusters

                               

      Troubleshooting Pods in Kubernetes Clusters In Kubernetes, pods can encounter various issues that prevent them from running correctly. Understanding the different pod states and how to troubleshoot them is essential for maintaining a healthy cluster. This guide covers common pod states and provides steps for diagnosing and resolving issues. Common Pod States: Init : The pod is initializing. All init containers must be completed before the main containers start. 0/1 Running : The pod is running, but not all containers are in a ready state. CrashLoopBackOff : The pod repeatedly fails and is restarted. Pending : The pod is waiting to be scheduled on a node. ImagePullBackOff : The pod cannot pull the container image. Troubleshooting Steps 1. Pod in Init State: When a pod is stuck in the Init state, it indicates that one or more init containers haven't completed successfully. Check Pod Description: kubectl -n sisense describe pod <pod_name> In the description, look for the init containers section. All init containers should ideally be in a "Completed" state. If one is still running or has failed, it can block the rest of the pod's containers from starting. Example: <span>Init Containers:</span><br/><span>  init-mongodb:</span><br/><span>    State:          Running</span> This indicates an issue with the init-mongodb container. Check Logs of Init Containers: If the init container is still running or has failed, check its logs: kubectl -n sisense logs <pod_name> -c init-mongodb After identifying issues in the init container, investigate the related services or dependencies, such as the MongoDB pod itself in this example. 2. Pod in 0/1, 1/2 Running State: This state indicates that the pod is running, but not all containers are in a ready state. Describe the Pod: kubectl -n sisense describe pod <pod_name> Check the State section for each container. Look for reasons why a container is not in a ready state, such as CrashLoopBackOff , ImagePullBackOff , or other errors. Check Logs for Previous Failed Container: If a container is in an error state or has restarted, checking the logs can provide more context about the issue. Current Logs: kubectl -n sisense logs <pod_name> -c <container_name> Replace <container_name> with the name of the specific container. Previous Logs: kubectl -n sisense logs <pod_name> -c <container_name> -p This command retrieves logs from the previous instance of the container, which can be particularly useful if the container has restarted. 3. Pod in CrashLoopBackOff State: A pod enters the CrashLoopBackOff state when it repeatedly fails and is restarted. To diagnose this issue: Describe the Pod: kubectl -n sisense describe pod <pod_name> This command provides detailed information, including the events and container statuses. Example: <span>State:          Waiting</span><br/><span>  Reason:       CrashLoopBackOff</span><br/><span>Last State:     Terminated</span><br/><span>  Reason:       OOMKilled</span> The OOMKilled reason indicates that the container was killed due to exceeding its memory limit. Increase the memory limit to fix the issue. Check Events and Container States: At the bottom of the describe output, you'll find the events section, which includes messages about why the pod failed. For example, FailedScheduling may indicate resource constraints or node issues. Review Logs: Logs can provide valuable insights when a pod is in the CrashLoopBackOff state. Check Current Logs: kubectl -n sisense logs <pod_name> This command retrieves the logs from the current running container. Check Previous Logs: kubectl -n sisense logs <pod_name> -p This command retrieves logs from the previous container instance, which is useful if the container was restarted. Specify the container name if the pod has multiple containers: kubectl -n sisense logs <pod_name> -c <container_name> -p 4. Pod in Pending State: If a pod is Pending, it means it hasn't been scheduled on a node yet. Check Pod Scheduling Events: kubectl -n sisense describe pod <pod_name> Look for events like: <span>Warning  FailedScheduling  85m   default-scheduler  0/3 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 2 node(s) didn't match pod anti-affinity rules.</span> This message indicates that the scheduler couldn't find a suitable node for the pod due to resource constraints, node affinity rules, or other scheduling policies. 5. Pod in ImagePullBackOff State: This state occurs when a pod cannot pull the container image from the registry. Check Pod Description for Image Issues: kubectl -n sisense describe pod <pod_name> Look for messages indicating issues with pulling the image, such as incorrect image names, tag issues, authentication problems, or network errors. For multinode deployments, note the server in the message.  It is possible that the image may not exist on all servers. Verify Image Name and Tag: Ensure that the image name and tag are correct and that the image is available in the specified registry. Check Image Pull Secrets: If the image is in a private registry, ensure that the registry is accessible. Manually Pull the Image: Sometimes, images are not available or cannot be downloaded within the default timeout. To verify the availability of the image and check for any errors, try pulling the image manually on the node specified in the error message: docker pull <image_name>:<tag> Replace <image_name> and <tag> with the appropriate image and tag names. This can help determine if the issue is with the image itself or the registry configuration. Conclusion By understanding these common pod states and following the troubleshooting steps, you can diagnose and resolve many issues in Kubernetes. Regularly monitoring pods and logs is essential for maintaining a stable and reliable Kubernetes environment. Check out this related content:  Documentation

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • TroubleshootingChevronRightIcon

      Resolving Issues with Updating EC2EC Passwords in Elasticube Connections

                                                               

      Resolving Issues with Updating EC2EC Passwords in Elasticube Connections Summary This article addresses the issue of updating EC2EC (Elasticube to Elasticube) passwords in the context of employee off-boarding or password changes.  Specifically, it covers the scenario where tables do not appear in the source cube after updating the password, preventing the completion of the update. Main Content Step-by-Step Instructions to Resolve the Issue Identify the Cube Details:   Identify the specific cube you are trying to connect from and to. Verify Connection Specificity: Determine if the issue affects all EC2EC connections or only specific ones. For example, if you can update the connection to one cube (e.g., locationsv2 ) but not another (e.g., ordersv3 ), note this discrepancy. Remove and Re-add the EC2EC Table: Remove the problematic EC2EC table from the source cube. Re-add the table to the source cube. Check for Hidden Columns: Ensure that all required columns in the source Elasticube are visible. If any required columns are hidden, make them visible. Rebuild the Cube: After making the necessary columns visible. Hidden columns in the source Elasticube can often cause issues with table visibility. Always check column visibility if tables do not appear as expected.  Rebuild the cube.  Verify that the tables now appear correctly in the source cube. Feedback and Support: If the issue persists, contact support with detailed information about the error and the context of its occurrence. Providing feedback on your experience can help improve the support process.   Check out this related content:  Academy Course Sisense Documentation

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • TroubleshootingChevronRightIcon

      Resolving Installation Failures with "Error occurred during validate_ssl_certs section"

                                       

      Resolving Installation Failures with "Error occurred during validate_ssl_certs section" Issue: During the installation process on a Linux system, you may encounter the error message: "Error occurred during validate_ssl_certs section." This error typically occurs when there is an issue with the SSL certificate being used. Solution: To resolve this issue, you need to validate the SSL certificate and ensure it matches the corresponding private key. This can be done using the openssl command-line tool, which is widely used for managing and validating SSL certificates. NOTE: You can use this guide as a general reference for validating SSL certificates in various contexts, whether during software installations, server setups, or routine security checks. Steps to Validate the SSL Certificate: Check if the Certificate and Key Files Exist: First, ensure that both the certificate and key files are present in the specified locations. <span>CERT_PATH="/path/to/your/certificate.crt"</span><br/><br/><span>KEY_PATH="/path/to/your/private.key"</span> <span>if [[ -f "$CERT_PATH" && -f "$KEY_PATH" ]]; then</span><br/><br/><span>  echo "Certificate and key files exist."</span><br/><br/><span>else</span><br/><br/><span>  echo "Certificate or key file does not exist."</span><br/><br/><span>  exit 1</span><br/><br/><span>fi</span> Validate the SSL Certificate: Use the following command to display the details of the certificate. This helps in verifying whether the certificate is properly formatted and valid. <span>openssl x509 -in "$CERT_PATH" -text -noout</span> <span>if [[ $? -eq 0 ]]; then</span><br/><br/><span>  echo "Certificate is valid."</span><br/><br/><span>else</span><br/><br/><span>  echo "Certificate is invalid."</span><br/><br/><span>  exit 1</span><br/><br/><span>fi</span> Check if the Certificate and Key Match: It's crucial that the SSL certificate matches the private key. The following commands compare the modulus of the certificate and key to ensure they match. <span>CERT_MODULUS=$(openssl x509 -noout -modulus -in "$CERT_PATH" | openssl md5)</span><br/><br/><span>KEY_MODULUS=$(openssl rsa -noout -modulus -in "$KEY_PATH" | openssl md5)</span> <span>if [[ "$CERT_MODULUS" == "$KEY_MODULUS" ]]; then</span><br/><br/><span>  echo "Certificate and key match."</span><br/><br/><span>else</span><br/><br/><span>  echo "Certificate and key do not match."</span><br/><br/><span>  exit 1</span><br/><br/><span>fi</span> Conclusion: If the certificate and key files exist, the certificate is valid, and the certificate matches the key, then the SSL setup is likely correct. If any of these checks fail, you may need to reissue or replace the certificate and key files. This should resolve the "Error occurred during validate_ssl_certs section" error and allow your installation process to proceed. Always ensure you use valid and correctly matched SSL certificates and keys to avoid such issues.   Check out this related content: Academy Course   Sisense Documentation 

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • TroubleshootingChevronRightIcon

      Installation fails with /bin/activate: No such file or directory

                                       

      Installation fails with /bin/activate: No such file or directory Sisense installation may fail in the very beginning with the following symptoms: [2024-06-18 18:07:02] Preparing System ...<br/><br/>[2024-06-18 18:07:02] Linux user: sistemas<br/><br/>[2024-06-18 18:07:02] Validating Sudo permissions for user sistemas ...<br/><br/>[2024-06-18 18:07:02] User sistemas has sufficient sudo permissions<br/><br/>[2024-06-18 18:07:02] Detecting Host OS  ...<br/><br/>[2024-06-18 18:07:02] OS: Ubuntu, Version: 23.04<br/><br/>[2024-06-18 18:07:02] Validating OS and its version<br/><br/>[2024-06-18 18:07:03] Validating that namespace name is all lower case ...<br/><br/>[2024-06-18 18:07:03] detected lock on dpkg, releasing it<br/><br/>Enter the password for user sisense and press [ENTER]:<br/><br/>Using password for Ansible Installation ...<br/><br/>[2024-06-18 18:07:15] Verifying Python packages exist ...<br/><br/>installer/01_general_preparation/functions.sh: line 615: sisense-installer/bin/activate: No such file or directory<br/><br/>[2024-06-18 18:08:39] ** Error occurred during generic_pre_install section **<br/><br/>[2024-06-18 18:08:39] Exiting Installation ... The issue is related to the wrong Python packages or not being installed. To resolve the issue it's needed to run the verification commands from the Minimum System Requirements:  https://docs.sisense.com/main/SisenseLinux/linux-minimum-requirements.htm#DefaultPackagesRepository <span>For Ubuntu:<br/></span><span>sudo apt-get update</span><br/><br/><span>sudo apt-get install python3 netcat sshpass python3-apt python3-pip dbus</span><br/><br/><span>sudo apt-get install jq</span><br/><br/><span>sudo python3 -m pip install --upgrade --force-reinstall pip</span><br/><br/><span>sudo python3 -m pip install configparser zipp</span><br/><br/><span>sudo python3 -m pip install -r kubespray/requirements.txt --ignore-installed (from the installation folder)</span> Check out this related content:  Academy Course Sisense Documentation

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • Sisense AdministrationChevronRightIcon

      How to Check SSL Ciphers

                                                       

      How to Check SSL Ciphers If you have enabled SSL on Sisense side, the Nginx controller will be deployed in the default namespace. To check the currently configured ciphers run the following command and check the "nginx.ingress.kubernetes.io/ssl-ciphers:" row:  <em>kubectl -n sisense describe ingress</em> <span>Name:             sisense-ingress</span><br/><br/><span>Labels:           app=api-gateway</span><br/><br/><span>                  app.kubernetes.io/managed-by=Helm</span><br/><br/><span>                  chart=api-gateway-2024.2.077</span><br/><br/><span>                  release=sisense</span><br/><br/><span>                  sisense-version=2024.2.077</span><br/><br/><span>Namespace:        sisense</span><br/><br/><span>Address:</span><br/><br/><span>Ingress Class:    <none></span><br/><br/><span>Default backend:  <default></span><br/><br/><span>TLS:</span><br/><br/><span>  sisense-tls terminates</span><br/><br/><span>Rules:</span><br/><br/><span>  Host                         Path  Backends</span><br/><br/><span>  ----                         ----  --------</span><br/><br/><span>  paragoninsgroup.sisense.com</span><br/><br/><span>                               /   api-gateway-external:8456 (10.42.140.227:8456)</span><br/><br/><span>Annotations:                   kubernetes.io/ingress.class: nginx</span><br/><br/><span>                               kubernetes.io/tls-acme: true</span><br/><br/><span>                               meta.helm.sh/release-name: sisense</span><br/><br/><span>                               meta.helm.sh/release-namespace: sisense</span><br/><br/><span>                               nginx.ingress.kubernetes.io/configuration-snippet: more_clear_headers Server;</span><br/><br/><span>                               nginx.ingress.kubernetes.io/proxy-body-size: 0m</span><br/><br/><span>                               nginx.ingress.kubernetes.io/proxy-read-timeout: 300</span><br/><br/><span>                               nginx.ingress.kubernetes.io/ssl-ciphers:</span><br/><br/><span>                                 </span><strong>ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS:!AESCCM</strong><br/><br/><span>                               nginx.ingress.kubernetes.io/ssl-prefer-server-ciphers: true</span> To decrypt the full list of the currently used ciphers use the string from the mentioned row with the following command: <span>openssl ciphers -v 'ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS:!AESCCM' | column -t</span>   Output Example: <span>ECDHE-RSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH    Au=RSA  Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH    Au=ECDSA Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>ECDH-RSA-AES256-GCM-SHA384   TLSv1.2 Kx=ECDH/RSA  Au=ECDH  Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>ECDH-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH/ECDSA Au=ECDH  Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>ECDHE-RSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH    Au=RSA  Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH    Au=ECDSA Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>ECDH-RSA-AES128-GCM-SHA256   TLSv1.2 Kx=ECDH/RSA  Au=ECDH  Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>ECDH-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH/ECDSA Au=ECDH  Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>DH-DSS-AES256-GCM-SHA384    TLSv1.2 Kx=DH/DSS   Au=DH   Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>DH-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=DH/RSA   Au=DH   Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>DHE-RSA-AES256-GCM-SHA384   TLSv1.2 Kx=DH     Au=RSA  Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>DH-DSS-AES128-GCM-SHA256    TLSv1.2 Kx=DH/DSS   Au=DH   Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>DH-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=DH/RSA   Au=DH   Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>DHE-RSA-AES128-GCM-SHA256   TLSv1.2 Kx=DH     Au=RSA  Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>ECDHE-RSA-AES256-SHA384    TLSv1.2 Kx=ECDH    Au=RSA  Enc=AES(256)   Mac=SHA384</span><br/><br/><span>ECDHE-ECDSA-AES256-SHA384   TLSv1.2 Kx=ECDH    Au=ECDSA Enc=AES(256)   Mac=SHA384</span><br/><br/><span>ECDHE-RSA-AES256-SHA      SSLv3  Kx=ECDH    Au=RSA  Enc=AES(256)   Mac=SHA1</span><br/><br/><span>ECDHE-ECDSA-AES256-SHA     SSLv3  Kx=ECDH    Au=ECDSA Enc=AES(256)   Mac=SHA1</span><br/><br/><span>ECDH-RSA-AES256-SHA384     TLSv1.2 Kx=ECDH/RSA  Au=ECDH  Enc=AES(256)   Mac=SHA384</span><br/><br/><span>ECDH-ECDSA-AES256-SHA384    TLSv1.2 Kx=ECDH/ECDSA Au=ECDH  Enc=AES(256)   Mac=SHA384</span><br/><br/><span>ECDH-RSA-AES256-SHA      SSLv3  Kx=ECDH/RSA  Au=ECDH  Enc=AES(256)   Mac=SHA1</span><br/><br/><span>ECDH-ECDSA-AES256-SHA     SSLv3  Kx=ECDH/ECDSA Au=ECDH  Enc=AES(256)   Mac=SHA1</span><br/><br/><span>DHE-RSA-AES256-SHA256     TLSv1.2 Kx=DH     Au=RSA  Enc=AES(256)   Mac=SHA256</span><br/><br/><span>DH-RSA-AES256-SHA256      TLSv1.2 Kx=DH/RSA   Au=DH   Enc=AES(256)   Mac=SHA256</span><br/><br/><span>DH-DSS-AES256-SHA256      TLSv1.2 Kx=DH/DSS   Au=DH   Enc=AES(256)   Mac=SHA256</span><br/><br/><span>DHE-RSA-AES256-SHA       SSLv3  Kx=DH     Au=RSA  Enc=AES(256)   Mac=SHA1</span><br/><br/><span>DH-RSA-AES256-SHA       SSLv3  Kx=DH/RSA   Au=DH   Enc=AES(256)   Mac=SHA1</span><br/><br/><span>DH-DSS-AES256-SHA       SSLv3  Kx=DH/DSS   Au=DH   Enc=AES(256)   Mac=SHA1</span><br/><br/><span>ECDHE-RSA-AES128-SHA256    TLSv1.2 Kx=ECDH    Au=RSA  Enc=AES(128)   Mac=SHA256</span><br/><br/><span>ECDHE-ECDSA-AES128-SHA256   TLSv1.2 Kx=ECDH    Au=ECDSA Enc=AES(128)   Mac=SHA256</span><br/><br/><span>ECDHE-RSA-AES128-SHA      SSLv3  Kx=ECDH    Au=RSA  Enc=AES(128)   Mac=SHA1</span><br/><br/><span>ECDHE-ECDSA-AES128-SHA     SSLv3  Kx=ECDH    Au=ECDSA Enc=AES(128)   Mac=SHA1</span><br/><br/><span>ECDH-RSA-AES128-SHA256     TLSv1.2 Kx=ECDH/RSA  Au=ECDH  Enc=AES(128)   Mac=SHA256</span><br/><br/><span>ECDH-ECDSA-AES128-SHA256    TLSv1.2 Kx=ECDH/ECDSA Au=ECDH  Enc=AES(128)   Mac=SHA256</span><br/><br/><span>ECDH-RSA-AES128-SHA      SSLv3  Kx=ECDH/RSA  Au=ECDH  Enc=AES(128)   Mac=SHA1</span><br/><br/><span>ECDH-ECDSA-AES128-SHA     SSLv3  Kx=ECDH/ECDSA Au=ECDH  Enc=AES(128)   Mac=SHA1</span><br/><br/><span>DHE-RSA-AES128-SHA256     TLSv1.2 Kx=DH     Au=RSA  Enc=AES(128)   Mac=SHA256</span><br/><br/><span>DH-RSA-AES128-SHA256      TLSv1.2 Kx=DH/RSA   Au=DH   Enc=AES(128)   Mac=SHA256</span><br/><br/><span>DH-DSS-AES128-SHA256      TLSv1.2 Kx=DH/DSS   Au=DH   Enc=AES(128)   Mac=SHA256</span><br/><br/><span>DHE-RSA-AES128-SHA       SSLv3  Kx=DH     Au=RSA  Enc=AES(128)   Mac=SHA1</span><br/><br/><span>DH-RSA-AES128-SHA       SSLv3  Kx=DH/RSA   Au=DH   Enc=AES(128)   Mac=SHA1</span><br/><br/><span>DH-DSS-AES128-SHA       SSLv3  Kx=DH/DSS   Au=DH   Enc=AES(128)   Mac=SHA1</span><br/><br/><span>AES256-GCM-SHA384       TLSv1.2 Kx=RSA     Au=RSA  Enc=AESGCM(256) Mac=AEAD</span><br/><br/><span>AES128-GCM-SHA256       TLSv1.2 Kx=RSA     Au=RSA  Enc=AESGCM(128) Mac=AEAD</span><br/><br/><span>AES256-SHA256         TLSv1.2 Kx=RSA     Au=RSA  Enc=AES(256)   Mac=SHA256</span><br/><br/><span>AES256-SHA           SSLv3  Kx=RSA     Au=RSA  Enc=AES(256)   Mac=SHA1</span><br/><br/><span>AES128-SHA256         TLSv1.2 Kx=RSA     Au=RSA  Enc=AES(128)   Mac=SHA256</span><br/><br/><span>AES128-SHA           SSLv3  Kx=RSA     Au=RSA  Enc=AES(128)   Mac=SHA1</span> Check out this related content: Academy course Sisense Documentation      

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • Sisense AdministrationChevronRightIcon

      Configuring/Adjusting Readiness Probes for Containers

                                       

      Configuring/Adjusting Readiness Probes for Containers Overview: Readiness probes are critical in container orchestration to ensure that containers are ready to handle traffic before they are included in service load balancing. If a container fails readiness probes due to insufficient thresholds, adjusting these parameters can help. This guide explains how to modify readiness probe settings to accommodate containers with longer startup times. Key Readiness Probe Parameters: initialDelaySeconds : Delay before the first probe is initiated after the container starts. periodSeconds : Interval between successive probes. timeoutSeconds : Maximum duration a probe can take before it is considered failed. failureThreshold : Number of consecutive failures allowed before the container is marked as unready. Recommended Configuration: To give the container more time to become available, the following settings can be adjusted: <span>readinessProbe:</span><br/><span>  failureThreshold: 5         # Allows more failures before marking the container as unready</span><br/><span>  httpGet:</span><br/><span>    path: /</span><br/><span>    port: metrics</span><br/><span>    scheme: HTTP</span><br/><span>  initialDelaySeconds: 30     # Provides 30 seconds before the first readiness probe</span><br/><span>  periodSeconds: 10           # Reduces the frequency of checks to every 10 seconds</span><br/><span>  successThreshold: 1         # Default, the number of successes needed to mark the container as ready</span><br/><span>  timeoutSeconds: 20          # Increases the time allowed for the probe to complete</span> Implementation: To apply these changes to your deployment, follow these steps: Access the Deployment's Edit Mode: kubectl -n sisense edit deployment <deployment_name> Replace <deployment_name> with the name of your specific deployment. Navigate to the readinessProbe Section: Scroll through the deployment configuration until you find the readinessProbe section. Adjust the parameters as needed, based on the recommended configuration above. Save Changes and Exit Edit Mode: After making the necessary adjustments, save the file and exit the edit mode. The deployment will automatically restart, applying the new readiness probe configuration. Conclusion: Proper configuration of readiness probes ensures that only containers ready to handle traffic are included in service routing. Adjusting initialDelaySeconds , periodSeconds , timeoutSeconds , and failureThreshold helps to manage containers with longer startup times effectively. By following the implementation steps, you can easily modify the probe settings and enhance the stability and reliability of your deployments.

      Vlad Solodkyi
      Vlad SolodkyiPosted 1 year ago
      0
               
      • Sisense AdministrationChevronRightIcon

      Adding a New Node to a Sisense Cluster

                               

      Adding a New Node to a Sisense Cluster The process of updating the nodes varies depending on your environment. Below, we outline the steps for both AWS EKS deployments with autoscaling enabled and self-hosted clusters. For AWS EKS Deployments with Autoscaling: If you deployed your Sisense cluster on AWS EKS and configured autoscaling in the Sisense installation configuration file ( autoscaling: true ), adding a new node is streamlined: Create a new node group in EKS with the desired configuration. Ensure the new node group is in the same availability zone as the node you intend to replace. Once the new node is up and running, it will automatically join your Sisense cluster. Safely drain and shut down the old node to complete the process. For Self-Hosted Clusters: If you are using a self-hosted cluster, follow these steps to add a new node: Adding the New Node: Open the cluster_config.yaml file. Set " new_node: yes " and " update: true ". Specify the details of the new node in the k8s_nodes section, adjusting the information as necessary. Here is an example: k8s_nodes:   - { node: 1, internal_ip: 0.0.0.0, external_ip: 0.0.0.0, disk_volume_device: /dev/sdb, roles: "application, query" }   - { node: 2, internal_ip: 0.0.0.0, external_ip: 0.0.0.0, disk_volume_device: /dev/sdb, roles: "application, query" }   - { node: 3, internal_ip: 0.0.0.0, external_ip: 0.0.0.0, disk_volume_device: /dev/sdb, roles: "build" }   - { node: new-node , internal_ip: 0.0.0.0, external_ip: 0.0.0.0, disk_volume_device: /dev/sdb, roles: "application, query" }   Run the installation script with the updated configuration file: ./sisense.sh cluster_config.yaml Removing the Old Node: In the cluster_config.yaml file, set " remove_node: true ". Specify the node to remove using " node_to_remove: node-name ". Run the installation script again to complete the removal process: ./sisense.sh cluster_config.yaml By following these steps, you can seamlessly expand your Sisense cluster, ensuring high availability and enhanced performance for your analytics workloads. If you have any questions or encounter any issues during this process, feel free to reach out to our support team for assistance.

      Vlad Solodkyi
      Vlad SolodkyiPosted 2 years ago
      0