diff --git a/.gitignore b/.gitignore index 55e8110..2fd2598 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -packer/build \ No newline at end of file +packer/build +bazel-bin +bazel-bladerunner +bazel-out +.gitignore +bazel-testlogs diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..b369316 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,4 @@ +load("@bazel_gazelle//:def.bzl", "gazelle") + +# gazelle:prefix git.wntrmute.dev/kyle/bladerunner +gazelle(name = "gazelle") diff --git a/WORKSPACE b/WORKSPACE index fe1531c..4ebade4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,9 +1,43 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") ## C++ dependencies +# http_archive( +# name = "gtest", +# url = "https://github.com/google/googletest/archive/release-1.10.0.zip", +# sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91", +# build_file = "@//:gtest.BUILD", +# ) + http_archive( - name = "gtest", - url = "https://github.com/google/googletest/archive/release-1.10.0.zip", - sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91", - build_file = "@//:gtest.BUILD", + name = "io_bazel_rules_go", + sha256 = "6b65cb7917b4d1709f9410ffe00ecf3e160edf674b78c54a894471320862184f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip", + "https://github.com/bazelbuild/rules_go/releases/download/v0.39.0/rules_go-v0.39.0.zip", + ], ) + +http_archive( + name = "bazel_gazelle", + sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz", + ], +) + + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository") + +############################################################ +# Define your own dependencies here using go_repository. +# Else, dependencies declared by rules_go/gazelle will be used. +# The first declaration of an external repository "wins". +############################################################ + +go_rules_dependencies() + +go_register_toolchains(version = "1.20.3") + +gazelle_dependencies() \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5140da2 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module git.wntrmute.dev/kyle/bladerunner + +go 1.20 + +require ( + git.sr.ht/~kisom/goutils v1.5.3 + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cf969e4 --- /dev/null +++ b/go.sum @@ -0,0 +1,94 @@ +bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= +git.sr.ht/~kisom/goutils v1.5.3 h1:Q0BI2Q8/gs5uYdi2e2op4uRt9hTLt5zgH9HdGDcx4pM= +git.sr.ht/~kisom/goutils v1.5.3/go.mod h1:azq1B7nq8lmva1FWeuw+tZ6xdJZCtJIlFskB3N2v9E4= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= +github.com/cloudflare/cfssl v1.5.0/go.mod h1:sPPkBS5L8l8sRc/IOO1jG51Xb34u+TYhL6P//JdODMQ= +github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= +github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcrypto v0.0.0-20200513165325-16679db567ff/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4= +github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4= +github.com/zmap/zlint/v2 v2.2.1/go.mod h1:ixPWsdq8qLxYRpNUTbcKig3R7WgmspsHGLhCCs6rFAM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/packer/build-image.sh b/packer/build-image.sh index 1d51767..6b4bb43 100755 --- a/packer/build-image.sh +++ b/packer/build-image.sh @@ -11,7 +11,7 @@ errmsg () { preflight () { case "${IMAGE_TYPE}" in - ubuntu) PACKER_BUILD_FILE="boards/raspberry-pi-4/ubuntu_server_20.04_arm64.json" ;; + ubuntu) PACKER_BUILD_FILE="boards/pi-cm4-ubuntu-22.04.2.json" ;; raspbian) PACKER_BUILD_FILE="boards/raspberry-pi/raspios-lite-arm.json" ;; custom) PACKER_BUILD_FILE="${2:-}" @@ -33,7 +33,6 @@ preflight () { } build_image () { - cd build/packer-builder-arm sudo packer build ${PACKER_BUILD_FILE} } diff --git a/packer/scripts/install-base.sh b/packer/scripts/install-base.sh new file mode 100755 index 0000000..407c673 --- /dev/null +++ b/packer/scripts/install-base.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +echo "==> Setting nameserver" +echo 'nameserver 8.8.8.8' > /etc/resolv.conf + +echo "==> installing base updates" +apt-get -y update +apt-get -y install ansible apt-transport-https ca-certificates +apt-get -y clean \ No newline at end of file diff --git a/packer/setup-env.sh b/packer/setup-env.sh new file mode 100644 index 0000000..08bcd4e --- /dev/null +++ b/packer/setup-env.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +IMAGE_TYPE="${1:-ubuntu}" + +select_image () { + case "${IMAGE_TYPE}" in + ubuntu) + PACKER_BUILD_FILE="boards/pi-cm4-ubuntu-22.04.2.json" ;; + REMOTE_IMAGE_URL="$(jq '.builders[0].file_urls' boards/pi-cm4-ubuntu-22.04.2.json | grep https | tr -d ' \"')" + + raspbian) PACKER_BUILD_FILE="boards/raspberry-pi/raspios-lite-arm.json" ;; + PACKER_BUILD_FILE="boards/pi-cm4-ubuntu-22.04.2.json" ;; + REMOTE_IMAGE_URL="$(jq '.builders[0].file_urls' boards/pi-cm4-ubuntu-22.04.2.json | grep https | tr -d ' \"')" + + custom) + PACKER_BUILD_FILE="${2:-}" + if [ -z "${PACKER_BUILD_FILE}" ] + then + errmsg "[!] custom board requires a board file path" + exit 1 + fi + ;; + *) + errmsg "[!] invalid image type ${IMAGE_TYPE}." + errmsg "[!] valid image types are" + errmsg " - raspbian" + errmsg " - ubuntu" + errmsg " - custom path/to/board/file" + exit 1 + ;; + esac +} \ No newline at end of file diff --git a/src/cmd/ubuntu-board-gen/BUILD.bazel b/src/cmd/ubuntu-board-gen/BUILD.bazel new file mode 100644 index 0000000..676e489 --- /dev/null +++ b/src/cmd/ubuntu-board-gen/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "ubuntu-board-gen_lib", + srcs = ["main.go"], + importpath = "git.wntrmute.dev/kyle/bladerunner/src/cmd/ubuntu-board-gen", + visibility = ["//visibility:private"], + deps = [ + "//src/packer", + "@ht_sr_git_kisom_goutils//die:go_default_library", + ], +) + +go_binary( + name = "ubuntu-board-gen", + embed = [":ubuntu-board-gen_lib"], + visibility = ["//visibility:public"], +) diff --git a/src/cmd/ubuntu-board-gen/main.go b/src/cmd/ubuntu-board-gen/main.go new file mode 100644 index 0000000..8ae4cd4 --- /dev/null +++ b/src/cmd/ubuntu-board-gen/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + + "git.sr.ht/~kisom/goutils/die" + "git.wntrmute.dev/kyle/bladerunner/src/packer" +) + +func main() { + var specFile, outputDir string + + flag.StringVar(&specFile, "f", "ubuntu-boards.yml", "`path` to Ubuntu board spec file") + flag.StringVar(&outputDir, "o", "", "`directory` to store Ubuntu boards") + flag.Parse() + + boardSpecs, err := packer.LoadUbuntuSpecs(specFile) + die.If(err) + + err = boardSpecs.WriteBoards(outputDir) + die.If(err) +} diff --git a/src/packer/BUILD.bazel b/src/packer/BUILD.bazel new file mode 100644 index 0000000..a035bd0 --- /dev/null +++ b/src/packer/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "packer", + srcs = [ + "board.go", + "builder.go", + "packer.go", + "provisioner.go", + "ubuntu.go", + ], + importpath = "git.wntrmute.dev/kyle/bladerunner/src/packer", + visibility = ["//visibility:public"], + deps = ["@in_gopkg_yaml_v2//:go_default_library"], +) diff --git a/src/packer/board.go b/src/packer/board.go new file mode 100644 index 0000000..faae423 --- /dev/null +++ b/src/packer/board.go @@ -0,0 +1,8 @@ +package packer + +type Board struct { + Variables interface{} `json:"variables"` + Builders []Builder `json:"builders"` + Provisioners []Provisioner `json:"provisioners"` + PostProcessors []string `json:"post-processors"` +} diff --git a/src/packer/builder.go b/src/packer/builder.go new file mode 100644 index 0000000..61d32ed --- /dev/null +++ b/src/packer/builder.go @@ -0,0 +1,192 @@ +package packer + +// RootPartitionSizes describes how big a single root partition should +// be given a standard 256M boot partition. This should cover standard +// install media sizes. +var RootPartitionSizes = map[string]string{ + "4G": "3.7G", + "8G": "7.7G", + "16G": "15.7G", + "32G": "31.7G", +} + +// Safe defaults that can be used when a custom option isn't needed. +const ( + QemuDefaultPath = "/usr/bin/qemu-aarch64-static" + RootPartitionSafeDefault = "2.8G" + StandardPath = "PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin" +) + +// Partition describes a partition in the generated image. +type Partition struct { + Name string `json:"name"` + Type string `json:"type"` + StartSector int `json:"start_sector"` // should be a uint64, except JSON + Size string `json:"size"` + Mountpoint string `json:"mountpoint"` +} + +// Builder specifically refers to an Arm builder as used for the images +// generated for a computeblade. +type Builder struct { + Type string `json:"type"` + FileURLs []string `json:"file_urls"` + FileChecksumURL string `json:"file_checksum_url"` + FileChecksumType string `json:"file_checksum_type"` + FileTargetExtension string `json:"file_target_extension"` + FileUnarchiveCommand []string `json:"file_unarchive_cmd"` + ImageBuildMethod string `json:"image_build_method"` + ImagePath string `json:"image_path"` + ImageSize string `json:"image_size"` + ImageType string `json:"image_type"` + ImagePartitions []Partition `json:"image_partitions"` + ImageChrootEnviron []string `json:"image_chroot_env"` + QemuBinarySourcePath string `json:"qemu_binary_source_path"` + QemuBinaryDestinationPath string `json:"qemu_binary_destination_path"` +} + +func NewBuilder(fileURLs []string) Builder { + return Builder{ + Type: "arm", + FileURLs: fileURLs, + } +} + +// With256MBootPartition returns a standard 256M boot partition. +func (b Builder) With256MBootPartition() Builder { + p := Partition{ + Name: "boot", + Type: "c", + StartSector: 2048, + Size: "256M", + Mountpoint: "/boot/firmware", + } + + b.ImagePartitions = append(b.ImagePartitions, p) + return b +} + +// WithSingleRootPartition should be called after With256MBootPartition, +// as it assumes a root partition immediately following the boot. +func (b Builder) WithSingleRootPartition(size string) Builder { + p := Partition{ + Name: "root", + Type: "83", + StartSector: 526336, + Size: size, + Mountpoint: "/", + } + + b.ImagePartitions = append(b.ImagePartitions, p) + return b +} + +// WithStandardImage generates a standard image with a boot and root partition. +func (b Builder) WithStandardImage(size string, outputPath string) Builder { + b.ImageBuildMethod = "reuse" + b.ImagePath = outputPath + b.ImageSize = size + b.ImageType = "dos" + + rootPartitionSize := RootPartitionSizes[size] + if rootPartitionSize == "" { + rootPartitionSize = RootPartitionSafeDefault + } + + b = b.With256MBootPartition() + b = b.WithSingleRootPartition(rootPartitionSize) + + // Append a standard PATH environment. + b.ImageChrootEnviron = append(b.ImageChrootEnviron, StandardPath) + + return b +} + +// WithStandard32GImage adds a standard 32G image spec to the Builder. +func (b Builder) WithStandard32GImage(outputPath string) Builder { + return b.WithStandardImage("32G", outputPath) +} + +// WithQemuDefaults sets the standard qemu path in the builder. +func (b Builder) WithQemuDefaults() Builder { + b.QemuBinarySourcePath = QemuDefaultPath + b.QemuBinaryDestinationPath = QemuDefaultPath + + return b +} + +// WithSHA256Checksum sets a SHA256 checksum URL. +func (b Builder) WithSHA256Checksum(url string) Builder { + b.FileChecksumURL = url + b.FileChecksumType = "sha256" + + return b +} + +func (b Builder) withXZ() Builder { + b.FileTargetExtension = "xz" + b.FileUnarchiveCommand = []string{ + "xz", + "--decompress", + } + + return b +} + +func (b Builder) withUnzip() Builder { + b.FileTargetExtension = "zip" + b.FileUnarchiveCommand = []string{ + "unzip", + } + + return b +} + +func (b Builder) withTGZ() Builder { + b.FileTargetExtension = "tgz" + b.FileUnarchiveCommand = []string{ + "tar", + "xzf", + } + + return b +} + +func (b Builder) withTarGZ() Builder { + b.FileTargetExtension = "tar.gz" + b.FileUnarchiveCommand = []string{ + "tar", + "xzf", + } + + return b +} + +func (b Builder) withGZ() Builder { + b.FileTargetExtension = "gz" + b.FileUnarchiveCommand = []string{ + "gunzip", + } + + return b +} + +// WithUnarchiver fills in the appropriate unarchiver command given the +// archive extension. +func (b Builder) WithUnarchiver(extension string) Builder { + switch extension { + case "xz": + b = b.withXZ() + case "zip": + b = b.withUnzip() + case "tgz": + b = b.withTGZ() + case "tar.gz": + b = b.withTarGZ() + case "gz": + b = b.withGZ() + } + + b.FileUnarchiveCommand = append(b.FileUnarchiveCommand, "$ARCHIVE_PATH") + return b +} diff --git a/src/packer/packer.go b/src/packer/packer.go new file mode 100644 index 0000000..eca0443 --- /dev/null +++ b/src/packer/packer.go @@ -0,0 +1,3 @@ +// Package packer contains utilities for interacting with Packer +// in the context of building images for the computeblade cluster. +package packer diff --git a/src/packer/provisioner.go b/src/packer/provisioner.go new file mode 100644 index 0000000..e15b095 --- /dev/null +++ b/src/packer/provisioner.go @@ -0,0 +1,78 @@ +package packer + +type Provisioner map[string]interface{} + +// ShellProvisioner provisions machines built by Packer using shell scripts. +func ShellProvisioner(scripts ...string) Provisioner { + return map[string]interface{}{ + "type": "shell", + "scripts": scripts, + } +} + +// ShellLocalProvisioner will run a shell script of your choosing on the +// machine where Packer is being run - in other words, shell-local will run +// the shell script on your build server, or your desktop, etc., rather than +// the remote/guest machine being provisioned by Packer. +func ShellLocalProvisioner(scripts ...string) Provisioner { + return map[string]interface{}{ + "type": "shell-local", + "scripts": scripts, + } +} + +// uploads files to machines built by Packer. The recommended usage of the file +// provisioner is to use it to upload files, and then use shell provisioner to +// move them to the proper place, set permissions, etc. +func FileProvisioner(src, dest string) Provisioner { + return map[string]interface{}{ + "type": "file", + "source": src, + "destination": dest, + } +} + +// BreakpointProvisioner pauses until the user presses "enter" to resume the build. +func BreakpointProvisioner(note string) Provisioner { + return map[string]interface{}{ + "type": "breakpoint", + "disable": false, + "note": note, + } +} + +func (b Board) WithShellProvisioner(scripts ...string) Board { + b.Provisioners = append(b.Provisioners, ShellProvisioner(scripts...)) + return b +} + +func (b Board) WithShellLocalProvisioner(scripts ...string) Board { + b.Provisioners = append(b.Provisioners, ShellLocalProvisioner(scripts...)) + return b +} + +func (b Board) WithFileProvisioner(src, dest string) Board { + b.Provisioners = append(b.Provisioners, FileProvisioner(src, dest)) + return b +} + +func (b Board) WithBreakpointProvisioner(note string) Board { + b.Provisioners = append(b.Provisioners, BreakpointProvisioner(note)) + return b +} + +type FileSet struct { + Source string `yaml:"source"` + Destination string `yaml:"destination"` +} + +func (fs FileSet) Provisioner() Provisioner { + return FileProvisioner(fs.Source, fs.Destination) +} + +func (b Board) WithFileSetsProvisioner(fileSets []FileSet) Board { + for _, fs := range fileSets { + b.Provisioners = append(b.Provisioners, fs.Provisioner()) + } + return b +} diff --git a/src/packer/ubuntu.go b/src/packer/ubuntu.go new file mode 100644 index 0000000..3a49616 --- /dev/null +++ b/src/packer/ubuntu.go @@ -0,0 +1,134 @@ +package packer + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "gopkg.in/yaml.v2" +) + +type urlSet struct { + FileURLs []string + ChecksumURL string +} + +// ubuntuImage returns a local cache path and a remote fetch path for an +// Ubuntu image for a given version. +func ubuntuImage(version string) urlSet { + imageBase := fmt.Sprintf("ubuntu-%s-preinstalled-server-arm64+raspi.img.xz", version) + imageDir := fmt.Sprintf("https://cdimage.ubuntu.com/releases/%s/release/", version) + checksumURL := fmt.Sprintf("http://cdimage.ubuntu.com/releases/%s/release/SHA256SUMS", version) + + localImage := "build/" + imageBase + remoteImage := imageDir + imageBase + + return urlSet{ + FileURLs: []string{ + localImage, + remoteImage, + }, + ChecksumURL: checksumURL, + } +} + +func UbuntuBuilder(version, size, outputPath string) Builder { + urlSet := ubuntuImage(version) + outputPath = "build/" + outputPath + + b := NewBuilder(urlSet.FileURLs) + return b. + WithSHA256Checksum(urlSet.ChecksumURL). + WithUnarchiver("xz"). + WithStandardImage(size, outputPath). + WithQemuDefaults() +} + +type UbuntuBoardSpec struct { + Version string `yaml:"version"` + Size string `yaml:"size"` + ImageName string `yaml:"name"` + LocalScripts []string `yaml:"local-scripts"` + Files []FileSet `yaml:"files"` + Scripts []string `yaml:"scripts"` +} + +func (spec UbuntuBoardSpec) JSONPath(base string) string { + dest := spec.ImageName + ext := filepath.Ext(dest) + if ext != "" { + ext = "." + ext + dest = strings.TrimSuffix(dest, ext) + } + + dest += ".json" + if base != "" { + dest = filepath.Join(base, dest) + } + return dest +} + +func (spec UbuntuBoardSpec) Board() Board { + board := Board{ + Variables: []string{}, + Builders: []Builder{ + UbuntuBuilder(spec.Version, spec.Size, spec.ImageName), + }, + } + + if len(spec.LocalScripts) != 0 { + board = board.WithShellLocalProvisioner(spec.LocalScripts...) + } + + if len(spec.Files) != 0 { + board = board.WithFileSetsProvisioner(spec.Files) + } + + if len(spec.Scripts) != 0 { + board = board.WithShellProvisioner(spec.Scripts...) + } + + return board +} + +func LoadUbuntuSpecs(path string) (specFile UbuntuBoardSpecFile, err error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return + } + + err = yaml.Unmarshal(data, &specFile) + if err != nil { + return + } + + return specFile, nil +} + +// UbuntuBoardSpecFile describes a set of specifications for Ubuntu boards. +// +// In the future, this may include additional data. +type UbuntuBoardSpecFile struct { + Boards []UbuntuBoardSpec `yaml:"boards"` +} + +func (specFile UbuntuBoardSpecFile) WriteBoards(outputDir string) error { + for _, spec := range specFile.Boards { + board := spec.Board() + dest := spec.JSONPath(outputDir) + + contents, err := json.Marshal(board) + if err != nil { + return err + } + + err = ioutil.WriteFile(dest, contents, 0644) + if err != nil { + return err + } + } + + return nil +}