use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Surface {
Local,
Tailnet,
}
impl Surface {
pub const fn prefix(self) -> &'static str {
match self {
Self::Local => "tailscale_",
Self::Tailnet => "tailnet_",
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Local => "local",
Self::Tailnet => "tailnet",
}
}
}
impl fmt::Display for Surface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub const TAILNET_VERBS: &[&str] = &[
"accept",
"approve",
"authorize",
"create",
"delete",
"disable",
"enable",
"expire",
"get",
"list",
"preview",
"rename",
"replace",
"resend",
"restore",
"rotate",
"set",
"suspend",
"test",
"update",
"validate",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Tier {
Read,
Write,
Destructive,
}
impl Tier {
pub const fn as_str(self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::Destructive => "destructive",
}
}
pub const fn flag(self) -> Option<&'static str> {
match self {
Self::Read => None,
Self::Write => Some("--allow-write"),
Self::Destructive => Some("--allow-destructive"),
}
}
}
impl fmt::Display for Tier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Toolset {
LocalStatus,
LocalPrefs,
LocalServe,
LocalFiles,
LocalLock,
LocalDebug,
LocalPassthrough,
TailnetDevices,
TailnetInvites,
TailnetLogging,
TailnetDns,
TailnetKeys,
TailnetPolicy,
TailnetPosture,
TailnetUsers,
TailnetSettings,
TailnetWebhooks,
TailnetServices,
TailnetOauthApps,
TailnetOrg,
}
impl Toolset {
pub const ALL: &'static [Toolset] = &[
Self::LocalStatus,
Self::LocalPrefs,
Self::LocalServe,
Self::LocalFiles,
Self::LocalLock,
Self::LocalDebug,
Self::LocalPassthrough,
Self::TailnetDevices,
Self::TailnetInvites,
Self::TailnetLogging,
Self::TailnetDns,
Self::TailnetKeys,
Self::TailnetPolicy,
Self::TailnetPosture,
Self::TailnetUsers,
Self::TailnetSettings,
Self::TailnetWebhooks,
Self::TailnetServices,
Self::TailnetOauthApps,
Self::TailnetOrg,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::LocalStatus => "local-status",
Self::LocalPrefs => "local-prefs",
Self::LocalServe => "local-serve",
Self::LocalFiles => "local-files",
Self::LocalLock => "local-lock",
Self::LocalDebug => "local-debug",
Self::LocalPassthrough => "local-passthrough",
Self::TailnetDevices => "tailnet-devices",
Self::TailnetInvites => "tailnet-invites",
Self::TailnetLogging => "tailnet-logging",
Self::TailnetDns => "tailnet-dns",
Self::TailnetKeys => "tailnet-keys",
Self::TailnetPolicy => "tailnet-policy",
Self::TailnetPosture => "tailnet-posture",
Self::TailnetUsers => "tailnet-users",
Self::TailnetSettings => "tailnet-settings",
Self::TailnetWebhooks => "tailnet-webhooks",
Self::TailnetServices => "tailnet-services",
Self::TailnetOauthApps => "tailnet-oauth-apps",
Self::TailnetOrg => "tailnet-org",
}
}
pub const fn surface(self) -> Surface {
match self {
Self::LocalStatus
| Self::LocalPrefs
| Self::LocalServe
| Self::LocalFiles
| Self::LocalLock
| Self::LocalDebug
| Self::LocalPassthrough => Surface::Local,
_ => Surface::Tailnet,
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|t| t.as_str() == s)
}
}
impl fmt::Display for Toolset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Annotations {
pub read_only: bool,
pub destructive: bool,
pub idempotent: bool,
pub open_world: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct ToolMeta {
pub name: &'static str,
pub toolset: Toolset,
pub tier: Tier,
pub summary: &'static str,
pub self_severing: bool,
pub severs_local_node: bool,
pub requires_confirmation: bool,
pub idempotent: bool,
pub varying_tier: bool,
pub min_version: Option<&'static str>,
pub platforms: Option<&'static [&'static str]>,
}
impl ToolMeta {
pub const fn surface(&self) -> Surface {
self.toolset.surface()
}
pub fn runs_here(&self) -> bool {
self.platforms
.is_none_or(|allowed| allowed.contains(&std::env::consts::OS))
}
pub const fn takes_confirmation(&self) -> bool {
self.requires_confirmation || self.self_severing || self.severs_local_node
}
pub const fn annotations(&self) -> Annotations {
Annotations {
read_only: !self.varying_tier && matches!(self.tier, Tier::Read),
destructive: self.varying_tier || matches!(self.tier, Tier::Destructive),
idempotent: self.idempotent,
open_world: true,
}
}
}