~/bend-docscommunity

cli/vazure.bend source

cli/vazure.bend on the hub · documented module

import Baseimport ../lib/effs/io.bend as Ximport ../lib/vstr.bend as Simport ./project.bend as Pimport bend-net-json@0.3.0.0/json.bend as Json# Only ARM/DevOps read commands with narrow JMESPath projections appear here.# Disable Azure CLI command logs: even metadata must not create files outside the project.def parsed(m: Maybe<&2, Json.Val>, label: String) -> IO(Json.Val):  match m:    case None{}: IO.die(Json.Val, 1, "snapshot: " ++ label ++ " returned invalid JSON")    case Some{value}: IO.pure(Json.Val, value)def result_json(result: Result<&1, &1, U32 & String, String>, label: String) -> IO(Json.Val):  match result:    case Fail{_}: IO.die(Json.Val, 1, "snapshot: " ++ label ++ " read failed")    case Done{raw}: parsed(Json.parse(raw), label)def read_json(label: String, command: String) -> IO(Json.Val):  do IO<Json.Val>:    result : Result<&1, &1, U32 & String, String> <- X.Proc.run("AZURE_LOGGING_ENABLE_LOG_FILE=false " ++ command ++ " 2>/dev/null")    result_json(result, label)def json_or_null(m: Maybe<&2, Json.Val>) -> Json.Val:  match m:    case None{}: Json.Null{}    case Some{v}: vdef at(v: Json.Val, k: String) -> Json.Val:  json_or_null(Json.get(v, k))def string(v: Json.Val) -> String:  match v:    case Json.Str{s}: s    case Json.Num{s}: s    case _: ""def array(v: Json.Val) -> List<&2, Json.Val>:  match v:    case Json.Arr{xs}: xs    case _: []def lit(s: String) -> String:  "\"" ++ S.esc(s) ++ "\""def quoted(xs: List<&2, String>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: lit(x) <> quoted(rest)def strings(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: string(x) <> strings(rest)def str_list(v: Json.Val) -> String:  "[" ++ String.join(quoted(strings(array(v))), ", ") ++ "]"def resource(+v: Json.Val) -> String:  "A.Resource{" ++ lit(string(at(v, "name"))) ++ ", " ++ lit(string(at(v, "type"))) ++ ", " ++ lit(string(at(v, "resourceGroup"))) ++ ", " ++ lit(string(at(v, "location"))) ++ "}"def project(v: Json.Val) -> String:  "A.Project{" ++ lit(string(at(v, "name"))) ++ "}"def repository(v: Json.Val) -> String:  "A.Repository{" ++ lit(string(at(v, "name"))) ++ "}"def pipeline(+v: Json.Val) -> String:  "A.Pipeline{" ++ lit(string(at(v, "name"))) ++ ", " ++ lit(string(at(v, "repository"))) ++ ", " ++ lit(string(at(v, "yaml"))) ++ "}"def connection(+v: Json.Val) -> String:  "A.ServiceConnection{" ++ lit(string(at(v, "name"))) ++ ", " ++ lit(string(at(v, "type"))) ++ ", " ++ lit(string(at(v, "scheme"))) ++ "}"def resource_items(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: resource(x) <> resource_items(rest)def group_names(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: string(at(x, "name")) <> group_names(rest)def project_items(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: project(x) <> project_items(rest)def repository_items(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: repository(x) <> repository_items(rest)def pipeline_items(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: pipeline(x) <> pipeline_items(rest)def connection_items(xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: connection(x) <> connection_items(rest)def permission(scope: String, +v: Json.Val) -> String:  "A.Permission{" ++ lit(scope) ++ ", " ++ str_list(at(v, "actions")) ++ ", " ++ str_list(at(v, "notActions")) ++ "}"def permission_items(+scope: String, xs: List<&2, Json.Val>) -> List<&2, String>:  match xs:    case []: []    case x <> rest: permission(scope, x) <> permission_items(scope, rest)type Rows is Data:  Rows{items: List<&2, String>, count: Nat}def row_items(r: Rows) -> List<&2, String>:  match r:    case Rows{items, _}: itemsdef row_count(r: Rows) -> Nat:  match r:    case Rows{_, count}: countdef add_rows(a: Rows, b: Rows) -> Rows:  match a b:    case Rows{as, ac} Rows{bs, bc}:      Rows{List.append(&2, String, as, bs), Nat.add(ac, bc)}# Identifiers come from Azure or a project's target fact. Reject shell metacharacters.def safe_chars(cs: List<&2, Char>) -> Bool:  match cs:    case []: True{}    case +c <> rest:      (Char.is_lower(c) || Char.is_upper(c) || Char.is_digit(c) ||       Char.is_eq(c, '-') || Char.is_eq(c, '_') || Char.is_eq(c, '.') ||       Char.is_eq(c, ':') || Char.is_eq(c, '/')) && safe_chars(rest)def safe(+s: String) -> Bool:  Bool.not(String.is_empty(s)) && safe_chars(String.to_list(s))def require_safe_bool(ok: Bool, label: String) -> IO(Unit):  match ok:    case True{}: IO.pure(Unit, Unit{})    case False{}: IO.die(Unit, 1, "snapshot: unsafe or empty " ++ label)def require_safe(label: String, s: String) -> IO(Unit):  require_safe_bool(safe(s), label)def permissions(+subscription: String, groups: List<&2, Json.Val>) -> IO(Rows):  match groups:    case []: IO.pure(Rows, Rows{[], 0n})    case group <> rest:      do IO<Rows>:        +name : String = string(at(group, "name"))        require_safe("resource group", name)        +url : String = "https://management.azure.com/subscriptions/" ++ subscription ++ "/resourceGroups/" ++ name ++ "/providers/Microsoft.Authorization/permissions?api-version=2015-07-01"        +data : Json.Val <- read_json("ARM permissions", "az rest --method get --url '" ++ url ++ "' --query 'value[].{actions:actions,notActions:notActions}' -o json")        more : Rows <- permissions(subscription, rest)        return add_rows(Rows{permission_items(name, array(data)), List.length(&2, Json.Val, array(data))}, more)def pipeline_details(+org: String, +project_name: String, xs: List<&2, Json.Val>) -> IO(Rows):  match xs:    case []: IO.pure(Rows, Rows{[], 0n})    case x <> rest:      do IO<Rows>:        +id : String = string(at(x, "id"))        require_safe("pipeline id", id)        detail : Json.Val <- read_json("DevOps pipeline", "az-devops pipelines show --organization '" ++ org ++ "' --project '" ++ project_name ++ "' --id " ++ id ++ " --query '{name:name,repository:repository.name || configuration.repository.name,yaml:process.yamlFilename || configuration.path}' -o json")        more : Rows <- pipeline_details(org, project_name, rest)        return add_rows(Rows{[pipeline(detail)], 1n}, more)def facts_text(+vlib: String, +org: String, +project_name: String, +resources: List<&2, String>, +groups: List<&2, String>, +permissions: List<&2, String>, +projects: List<&2, String>, +repos: List<&2, String>, +pipelines: List<&2, String>, +connections: List<&2, String>, +arm_identity: String, +devops_identity: String) -> String:  "import Base\nimport " ++ vlib ++ "/azure.bend as A\n\n# Read-only Azure and Azure DevOps facts. Generated by v system snapshot; do not edit.\n" ++  "def organization() -> String:\n  " ++ lit(org) ++ "\n\ndef devops_project() -> String:\n  " ++ lit(project_name) ++ "\n\n" ++  "def resources() -> List<&2, A.Resource>:\n  [" ++ String.join(resources, ", ") ++ "]\n\n" ++  "def resource_groups() -> List<&2, String>:\n  [" ++ String.join(groups, ", ") ++ "]\n\n" ++  "def permissions() -> List<&2, A.Permission>:\n  [" ++ String.join(permissions, ", ") ++ "]\n\n" ++  "def projects() -> List<&2, A.Project>:\n  [" ++ String.join(projects, ", ") ++ "]\n\n" ++  "def repositories() -> List<&2, A.Repository>:\n  [" ++ String.join(repos, ", ") ++ "]\n\n" ++  "def pipelines() -> List<&2, A.Pipeline>:\n  [" ++ String.join(pipelines, ", ") ++ "]\n\n" ++  "def service_connections() -> List<&2, A.ServiceConnection>:\n  [" ++ String.join(connections, ", ") ++ "]\n\n" ++  "def identities() -> List<&2, A.Identity>:\n  [A.Identity{\"azure\", " ++ lit(arm_identity) ++ "}, A.Identity{\"devops\", " ++ lit(devops_identity) ++ "}]\n"def snapshot(+f: P.Facts) -> IO(Unit):  do IO<Unit>:    +org : String = P.azure_organization(f)    +project_name : String = P.azure_project(f)    require_safe("DevOps organization", org)    require_safe("DevOps project", project_name)    +account : Json.Val <- read_json("Azure account", "az account show --query '{name:user.name,id:id}' -o json")    +subscription : String = string(at(account, "id"))    require_safe("subscription", subscription)    +resources : Json.Val <- read_json("ARM resources", "az resource list --query '[].{name:name,type:type,resourceGroup:resourceGroup,location:location}' -o json")    +groups : Json.Val <- read_json("ARM resource groups", "az group list --query '[].{name:name}' -o json")    +grants : Rows <- permissions(subscription, array(groups))    +projects : Json.Val <- read_json("DevOps projects", "az-devops devops project list --organization '" ++ org ++ "' --query 'value[].{name:name}' -o json")    +repos : Json.Val <- read_json("DevOps repositories", "az-devops repos list --organization '" ++ org ++ "' --project '" ++ project_name ++ "' --query '[].{name:name}' -o json")    pipeline_list : Json.Val <- read_json("DevOps pipelines", "az-devops pipelines list --organization '" ++ org ++ "' --project '" ++ project_name ++ "' --query '[].{id:id,name:name}' -o json")    +pipeline_rows : Rows <- pipeline_details(org, project_name, array(pipeline_list))    +connections : Json.Val <- read_json("DevOps service connections", "az-devops devops service-endpoint list --organization '" ++ org ++ "' --project '" ++ project_name ++ "' --query '[].{name:name,type:type,scheme:authorization.scheme}' -o json")    profile : Json.Val <- read_json("DevOps profile", "cat ~/.config/azure-plugin/profiles.json")    +devops_identity : String = string(at(at(profile, "devops"), "username"))    X.write_file("azure.bend", facts_text(P.vlib_of(f), org, project_name, resource_items(array(resources)), quoted(group_names(array(groups))), row_items(grants), project_items(array(projects)), repository_items(array(repos)), row_items(pipeline_rows), connection_items(array(connections)), string(at(account, "name")), devops_identity))    IO.print("snapshot: " ++ Nat.show(List.length(&2, Json.Val, array(resources))) ++ " Azure resources, " ++ Nat.show(row_count(grants)) ++ " permission entries, " ++ Nat.show(List.length(&2, Json.Val, array(groups))) ++ " resource groups; " ++ Nat.show(List.length(&2, Json.Val, array(projects))) ++ " DevOps projects, " ++ Nat.show(List.length(&2, Json.Val, array(repos))) ++ " repos, " ++ Nat.show(row_count(pipeline_rows)) ++ " pipelines, " ++ Nat.show(List.length(&2, Json.Val, array(connections))) ++ " service connections")