Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,5 @@ sample_data/*



.govbot
govbot.yml
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,42 @@ govbot update # update govbot to latest version
govbot --help # see all commands and options
```

### Docker

* Build the image from source:

```bash
docker build -t govbot:latest -f actions/govbot/Dockerfile actions/govbot/
```

* Run the setup wizard (writes `govbot.yml` and other config files to the current directory):

```bash
docker run --rm -it -v $(pwd):/home/govbot govbot:latest
```

Mounting `$(pwd)` to `/home/govbot` ensures config files and downloaded data persist between runs.

#### Common Docker Commands

```bash
# Run the full pipeline (clone, tag, build) using govbot.yml config
docker run --rm -v $(pwd):/home/govbot govbot:latest

# Clone a specific state (or use "all" for all states)
docker run --rm -v $(pwd):/home/govbot govbot:latest clone il

# Stream legislative activity logs
docker run --rm -v $(pwd):/home/govbot govbot:latest logs

# Build RSS feeds (requires tags defined in govbot.yml)
docker run --rm -v $(pwd):/home/govbot govbot:latest build

# Delete all cloned repos, then re-clone only the state in govbot.yml
docker run --rm -v $(pwd):/home/govbot govbot:latest delete all
docker run --rm -v $(pwd):/home/govbot govbot:latest
```

# 🏛️ Govbot Legislation Effort

- Nearly all state governments
Expand Down
31 changes: 31 additions & 0 deletions actions/govbot/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build artifacts
target/
# Keep Cargo.lock for reproducible builds
# Cargo.lock

# IDE
.idea/
.vscode/
*.swp
*.swo

# Git
.git/
.gitignore

# Docs
docs/

# Other actions
../format/
../scrape/
../extract/
../format/
../pipeline-manager/
../report-publisher/

# Mocks
mocks/

# Snapshots
__snapshots__/
44 changes: 44 additions & 0 deletions actions/govbot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# syntax=docker/dockerfile:1

# ── Stage 1: Build ────────────────────────────────────────────────
FROM docker.io/library/rust:1-slim AS builder

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
libssl-dev \
libgit2-dev \
libonnx-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /home/govbot

# Copy dependency manifests first (layer caching)
COPY Cargo.toml Cargo.lock ./

# Fetch dependencies (populates Cargo cache)
RUN cargo fetch

# Copy full source
COPY . .

# Build release binary
RUN cargo build --release --bin govbot

# ── Stage 2: Runtime ─────────────────────────────────────────────
# Use trixie-slim to match the glibc version in rust:1-slim (builder)
FROM docker.io/library/debian:trixie-slim AS runtime

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /home/govbot

COPY --from=builder \
/home/govbot/target/release/govbot \
/usr/local/bin/govbot

ENTRYPOINT ["govbot"]
46 changes: 31 additions & 15 deletions actions/govbot/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ use anyhow::{Context, Result};
use std::path::Path;
use std::process::{Command, Stdio};

/// Check if the config has non-empty tags defined.
fn has_tags(config: &serde_json::Value) -> bool {
config.get("tags")
.and_then(|t| t.as_object())
.map(|o| !o.is_empty())
.unwrap_or(false)
}

/// Run the full govbot pipeline: clone/update → tag → build.
///
/// Smart update behavior:
Expand All @@ -15,6 +23,9 @@ pub fn run_pipeline(config_path: &Path) -> Result<()> {
.parent()
.unwrap_or_else(|| Path::new("."));

// Load config once for reuse throughout the pipeline
let config = crate::publish::load_config(config_path)?;

let repos_dir = cwd.join(".govbot").join("repos");
let has_repos = repos_dir.exists()
&& std::fs::read_dir(&repos_dir)
Expand All @@ -37,7 +48,6 @@ pub fn run_pipeline(config_path: &Path) -> Result<()> {
.status()
} else {
// First run: clone based on config
let config = crate::publish::load_config(config_path)?;
let repos = crate::publish::get_repos_from_config(&config);

let mut cmd = Command::new(&govbot_bin);
Expand Down Expand Up @@ -78,22 +88,28 @@ pub fn run_pipeline(config_path: &Path) -> Result<()> {
_ => {}
}

// Step 3: Build RSS feeds
eprintln!();
eprintln!("=== Step 3/3: Building RSS feeds ===");
eprintln!();
// Step 3: Build RSS feeds (only if tags are defined)
if has_tags(&config) {
eprintln!();
eprintln!("=== Step 3/3: Building RSS feeds ===");
eprintln!();

let build_status = Command::new(&govbot_bin)
.arg("build")
.current_dir(cwd)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to run govbot build")?;
let build_status = Command::new(&govbot_bin)
.arg("build")
.current_dir(cwd)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("Failed to run govbot build")?;

if !build_status.success() {
anyhow::bail!("Build step failed with exit code: {}", build_status.code().unwrap_or(-1));
if !build_status.success() {
anyhow::bail!("Build step failed with exit code: {}", build_status.code().unwrap_or(-1));
}
} else {
eprintln!();
eprintln!("=== Step 3/3: Skipping RSS feed build ===");
eprintln!("No tags defined in govbot.yml. Add tags, then run 'govbot build' to generate feeds.");
}

eprintln!();
Expand Down