{"id":16927469,"url":"https://github.com/dirien/trivy-plugin-ui","last_synced_at":"2025-04-11T17:43:04.087Z","repository":{"id":103803915,"uuid":"554067755","full_name":"dirien/trivy-plugin-ui","owner":"dirien","description":"Simple Trivy UI plugin written in Rust","archived":false,"fork":false,"pushed_at":"2022-10-22T15:02:56.000Z","size":32,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T23:08:57.329Z","etag":null,"topics":["plugin","rust","trivy","ui"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dirien.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-19T07:42:40.000Z","updated_at":"2023-06-15T09:41:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"ed94e88a-6718-4805-a223-e50cfa1fdaaa","html_url":"https://github.com/dirien/trivy-plugin-ui","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirien%2Ftrivy-plugin-ui","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirien%2Ftrivy-plugin-ui/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirien%2Ftrivy-plugin-ui/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dirien%2Ftrivy-plugin-ui/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dirien","download_url":"https://codeload.github.com/dirien/trivy-plugin-ui/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248451670,"owners_count":21105912,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["plugin","rust","trivy","ui"],"created_at":"2024-10-13T20:34:18.453Z","updated_at":"2025-04-11T17:43:04.077Z","avatar_url":"https://github.com/dirien.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"## TL;DR Code\n\n%[https://github.com/dirien/trivy-plugin-ui]\n\n## Introduction\n\nIn this blog article, we're going to build a `Trivy` plugin using `Rust` 🦀. The functionality of the plugin itself is pretty basic: It contains a simple terminal user interface (TUI) to display the results of a `Trivy` image scan.\n\nThe main motivation of this blog article, is to show how to put the learnings of my last articles into a real-world scenario. We will use `jReleaser` again, to create our release and upload the binaries to `GitHub`.\n\nFeel free to read the blog, as we are not dig so deep into the details of `jReleaser`.\n\n%[https://blog.ediri.io/how-to-release-rust-apps-with-jreleaser]\n\nWe're going to use also an external library called `tui-rs` to create the UI. And of course most `Rust` 🦀 language elements.\n\nCheck my blog, to build up an basic understanding of the most common language elements of `Rust` 🦀\n\n%[https://blog.ediri.io/learn-rust-in-under-10-mins]\n\n## Prerequisites\n\nBe sure that you have the following tools installed and configured:\n\n- [jReleaser](https://jreleaser.org)\n- [Rust](https://www.rust-lang.org)\n- [Trivy](https://trivy.dev/)\n\n## What is a `Trivy` plugin?\n\n### First, what is `Trivy` at all?\n\n`Trivy` (tri pronounced like trigger, vy pronounced like envy) is a simple and comprehensive\nvulnerability/misconfiguration/secret scanner for containers and other artifacts developed by Aqua Security.\n\nTo know more about `Trivy`, I highly recommend to check following videos and of course\nthe [official documentation](https://aquasecurity.github.io/trivy).\n\n%[https://youtu.be/-IH5inFyEqU]\n\n%[https://youtu.be/bgYrhQ6rTXA]\n\n%[https://youtu.be/6Vw0QgJ-k5o]\n\nOr follow the Anaïs on Twitter:\n\n%[https://twitter.com/urlichsanais]\n\n### Now what are `Trivy` plugins?\n\n`Trivy` provides a plugin feature to allow others to extend the `Trivy` CLI without the need to change the `Trivy` code base. This plugin system was inspired by the plugin system used in kubectl, Helm, and Conftest.\n\n- They can be added and removed from a `Trivy` installation without impacting the core `Trivy` tool.\n- They can be written in any programming language.\n- They integrate with `Trivy`, and will show up in `Trivy` help and subcommands.\n\nA plugin can be installed using the `trivy plugin install` command. This command takes a url and will download the plugin and install it in the plugin cache.\n\n## Welcome `tui-rs`\n\nOn of the major parts of this plugin is the terminal user interface. We will use the [tui-rs](https://github.com/fdehau/tui-rs) library to create our TUI in the plugin. The library is very easy to use and provides a lot of out of the box widgets to create a nice looking TUI.\n\nI used the [lazytrivy](https://github.com/owenrumney/lazytrivy) from [Owen Rumney](https://twitter.com/owenrum) as a starting point for the TUI. Lazytrivy is a wrapper for `Trivy` that allows you to run `Trivy` without remembering the command arguments and renders everything nicely in a TUI.\n\n%[https://twitter.com/owenrum/status/1572536592270491649?s=20\u0026t=BuyZXv7XGUHfClDEJGT8EQ]\n\n\u003e BTW, lazytrivy is written in Go, so nice to rebuild some parts in `Rust` 🦀 with this plugin.\n\n## Create the plugin\n\n### Initialize the project\n\nLet us jump straight into the code. We will create a new `Rust` 🦀 project with the following command:\n\n```bash\ncargo init\n```\n\nAnd add the following dependencies to the `Cargo.toml` file:\n\n```bash\ncargo add tui\ncargo add serde_json\ncargo add serde\ncargo add clap\n```\n\nThe `tui` library is the main library we will use to create the TUI. The `serde_json` and `serde` libraries are used to parse the `JSON` output of `Trivy`. And the `clap` library is used to parse the command line arguments.\n\nUnderneath the `src` directory I created the `main.rs` and some additional `Rust` 🦀 files to keep the code clean and readable.\n\nLet us take a look into some parts of the `Rust` 🦀 files, as we can not cover the whole code in this blog article.\n\n### The `trivy.rs` file\n\nThe task of the `trivy.rs` file is to execute the `Trivy` CLI and parse the `JSON` output.\n\nThe output of the command is passed to the `serde` library to parse the `JSON` into a struct. The struct is defined in the `trivy.rs` file as well.\n\n```rust\npub fn trivy(image_name: \u0026str) -\u003e Trivy {\n    // setup terminal\n    let mut cmd = Command::new(\"trivy\");\n    let list = cmd.arg(\"image\").arg(image_name)\n        .arg(\"--format\").arg(\"json\")\n        .output();\n\n    let object: Trivy;\n    match list {\n        Ok(out) =\u003e match String::from_utf8(out.stdout) {\n            Ok(data) =\u003e {\n                object = serde_json::from_str(\u0026data.as_str()).unwrap();\n            }\n            Err(_) =\u003e unreachable!(\"No panic happens in this block\"),\n        }\n        Err(_) =\u003e unreachable!(\"No panic happens in this block\"),\n    }\n    object\n}\n```\n\n\u003e As you can see, we only check for container image vulnerabilities. Feel free to add more scan options.\n\n### The `cli.rs` file\n\nThe `cli.rs` file contains the code to handle all the command line releated task. To keep our life simple, we use the `clap` library.\n\nIn the `main` function, we parse the arguments and pass the image name to the `trivy.rs` file to run the `Trivy` CLI.\n\nIn the code, we define the argument structure:\n\n```rust\n#[derive(Parser, Debug)]\n#[command(author = \"Engin Diri\", version, long_about = None)]\n/// A simple tui Trivy plugin written in Rust\npub struct Args {\n    #[arg(short, long)]\n    pub image_name: String,\n}\n```\n\nAnd parse the arguments and pass it to the trivy command with the following code:\n\n```rust\nlet args = Args::parse();\nlet object = trivy::trivy( \u0026 args.image_name);\n```\n\n### The `ui.rs` file\n\nIn the `ui.rs` file, we create the TUI. The TUI consists of a table with the vulnerabilities. If you press the enter key on a vulnerability, the details of the vulnerability are shown in a new popup window.\n\nThe table with the vulnerabilities also has some styling. The vulnerabilities are colored based on the severity of the vulnerability.\n\n```rust\npub fn critical_color(crtical: String) -\u003e Style {\n    return if crtical == \"CRITICAL\" {\n        Style::default().fg(Color::Red)\n    } else if crtical == \"HIGH\" {\n        Style::default().fg(Color::Yellow)\n    } else if crtical == \"MEDIUM\" {\n        Style::default().fg(Color::Blue)\n    } else if crtical == \"LOW\" {\n        Style::default().fg(Color::Green)\n    } else {\n        Style::default().fg(Color::White)\n    };\n}\n```\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666360549603/XlcIq4YQr.png align=\"center\")\n\nThe creation of the rows of the table is done with the following code:\n\n```rust\nfor i in \u0026app.trivy.results {\n    match \u0026i.vulnerabilities {\n        Some(vul) =\u003e {\n            rows.push((Row::new(vec![\n                Span::styled(\"\", Style::default()),\n            ]), std::ptr::null()));\n            rows.push((Row::new(vec![\n                Span::styled(\"\", Style::default()),\n                Span::styled(\"target: \", Style::default().fg(Color::White).add_modifier(Modifier::BOLD)),\n                Span::styled(i.target.clone(), Style::default().fg(Color::Blue)),\n            ]), std::ptr::null()));\n            rows.push((Row::new(vec![\n                Span::styled(\"\", Default::default()),\n            ]), std::ptr::null()));\n            for j in vul {\n                rows.push((Row::new(vec![\n                    Cell::from(j.severity.clone().unwrap_or(\"None\".to_string())).style(lib::critical_color(j.severity.clone().unwrap_or(\"None\".to_string()))),\n                    Cell::from(j.vulnerability_id.clone().unwrap_or(\"None\".to_string())).style(Style::default().fg(Color::White)),\n                    Cell::from(j.title.clone().unwrap_or(\"None\".to_string())).style(Style::default().fg(Color::White)),\n                ]), j));\n            }\n        }\n        _ =\u003e {}\n    }\n}\n```\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666360577996/ovSMET33d.png align=\"center\")\n\nThe interaction with the TUI is done with the following code:\n\n```rust\nif let Event::Key(key) = event::read()? {\n    match key.code {\n    KeyCode::Char('q') =\u003e {\n    if app.show_popup {\n      app.pop_scroll = 0;\n      app.show_popup = false;\n    } else {\n      return Ok(());\n    }\n  }\n  KeyCode::Esc =\u003e {\n    if app.show_popup {\n      app.pop_scroll = 0;\n      app.show_popup = false;\n    } else {\n      return Ok(());\n    }\n  }\n  KeyCode::Down =\u003e {\n    if app.show_popup {\n      app.pop_scroll += 1;\n    } else {\n      app.next();\n    }\n  }\n  KeyCode::Up =\u003e {\n    if app.show_popup {\n      if app.pop_scroll \u003e 0 {\n        app.pop_scroll -= 1;\n      }\n    } else {\n      app.previous();\n    }\n  }\n  KeyCode::Enter =\u003e app.show_popup = !app.show_popup,\n          _ =\u003e {}\n  }\n}\n```\n\nHere is a screenshot of the TUI:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666359301084/4-hi2hz-0.png align=\"center\")\n\n### The `plugin.yaml`\n\nA `Trivy` plugin has a top-level directory, and then a plugin.yaml file.\n\nThe core of a plugin is a simple YAML file named plugin.yaml. Here is the content of the plugin.yaml file:\n\n```yaml\nname: \"trivy-ui\"\nrepository: github.com/dirien/trivy-plugin-ui\nversion: \"0.1.0\"\nusage: trivy ui -- --image-name \u003cimage-name\u003e\ndescription: |-\n  A Trivy plugin that displays the vulnerabilities in a TUI.\n  Usage: trivy ui -- --image-name \u003cimage-name\u003e\nplatforms:\n  - selector:\n      os: linux\n      arch: amd64\n    uri: https://github.com/dirien/trivy-plugin-ui/releases/download/v0.1.0/ui-0.1.0-linux-amd64.tar.gz\n    bin: ./ui\n  - selector:\n      os: darwin\n      arch: amd64\n    uri: https://github.com/dirien/trivy-plugin-ui/releases/download/v0.1.0/ui-0.1.0-darwin-amd64.tar.gz\n    bin: ./ui\n```\n\nThe `plugin.yaml` field should contain the following information:\n\n- `name`: The name of the plugin. This also determines how the plugin will be made available in the `Trivy` CLI. (\n  required)\n- `version`: The version of the plugin. (required)\n- `usage`: A short usage description. (required)\n- `description`: A long description of the plugin. This is where you could provide a helpful documentation of your\n  plugin. (required)\n- `platforms`: (required)\n- `selector`: The OS/Architecture specific variations of a execution file. (optional)\n  - `os`: OS information based on GOOS (linux, darwin, etc.) (optional)\n  - `arch`: The architecture information based on GOARCH (amd64, arm64, etc.) (optional)\n- `uri`: Where the executable file is. Relative path from the root directory of the plugin or remote URL such as HTTP\n  and\n  S3. (required)\n- `bin`: Which file to call when the plugin is executed. Relative path from the root directory of the plugin. (required)\n\n### Creating the release with `jReleaser`\n\nTo create the release, we use the [jReleaser](https://jreleaser.org/) tool. Please check my previous blog post on how to use `jReleaser` to create a release.\n\nThe `jReleaser` configuration file is the following:\n\n```yaml\nproject:\n  name: ui\n  version: 0.1.0\n  description: A simple tui Trivy plugin written in Rust\n  authors:\n    - Engin Diri\n  license: Apache-2.0\n  inceptionYear: 2022\n\nenvironment:\n  properties:\n    artifactsDir: out/jreleaser/assemble/ui/archive\n\nplatform:\n  replacements:\n    'osx-x86_64': 'darwin-amd64'\n    'linux-x86_64': 'linux-amd64'\n    'windows-x86_64': 'windows-amd64'\n\nassemble:\n  archive:\n    ui:\n      active: ALWAYS\n      formats: [ TAR_GZ ]\n      attachPlatform: true\n      fileSets:\n        - input: 'target/release'\n          includes: [ 'ui{.exe,}' ]\n        - input: '.'\n          includes: [ 'LICENSE' ]\n\ndistributions:\n  ui:\n    type: BINARY\n    executable:\n      windowsExtension: exe\n    artifacts:\n      - path: '{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-darwin-amd64.tar.gz'\n        platform: 'osx-x86_64'\n      - path: '{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-linux-amd64.tar.gz'\n        platform: 'linux-x86_64'\n      - path: '{{artifactsDir}}/{{distributionName}}-{{projectVersion}}-windows-amd64.tar.gz'\n        platform: 'windows-x86_64'\n\nrelease:\n  github:\n    owner: dirien\n    name: trivy-plugin-ui\n    skipTag: false\n    draft: false\n    update:\n      enabled: true\n      sections:\n        - ASSETS\n        - TITLE\n        - BODY\n```\n\nNow, with everything in place, we can create our release in `GitHub`.\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666359204718/a1BVqptUl.png align=\"center\")\n\nUnder the `Releases` tab, you should see the `v0.1.0` release:\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666359265636/3j61wHbzY.png align=\"center\")\n\n\n### Installing the plugin\n\nLet's install the plugin:\n\n```bash\ntrivy plugin install github.com/dirien/trivy-plugin-ui\n2022-10-21T11:24:36.868+0200\tINFO\tInstalling the plugin from github.com/dirien/trivy-plugin-ui...\n2022-10-21T11:24:38.614+0200\tINFO\tLoading the plugin metadata...\n```\n\nAnd see the plugin in the list:\n\n```bash\ntrivy plugin list\nInstalled Plugins:\n  Name:    trivy-ui\n  Version: 0.1.0\n```\n\nAnd execute the plugin:\n\n```bash\ntrivy trivy-ui -- --image-name dexidp/dex:latest\n```\n\n![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666358299511/XBvdBHTpQ.png align=\"center\")\n\n## Wrap-Up\n\nIn this blog post, we saq how to create a `Trivy` plugin using `Rust` 🦀. Plus, we used a TUI library to create a simple and good-looking TUI.\n\nNext, we used `jReleaser` to create a release. And finally, we installed the plugin and ran it.\n\nFeel free to check the source code of the plugin to create your own plugin or see how I created a TUI with `Rust` 🦀.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirien%2Ftrivy-plugin-ui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdirien%2Ftrivy-plugin-ui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdirien%2Ftrivy-plugin-ui/lists"}