diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d3ffc5a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +name: goreleaser + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.14 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml new file mode 100644 index 0000000..48e3b18 --- /dev/null +++ b/.github/workflows/run_test.yml @@ -0,0 +1,25 @@ +name: test + +on: + push: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: setup Go + uses: actions/setup-go@v2 + with: + go-version: 1.14 + - name: get dependencies + run: go get -v -t -d ./... + - name: setup aws credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-1 + - name: run test + run: go test -v ./lib/... diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..bc7bae7 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,35 @@ +project_name: abc +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +builds: + - main: . + binary: abc + goos: + - windows + - darwin + - linux + goarch: + - amd64 + - 386 + ldflags: + - -s -w + - -X main.Version={{.Version}} + env: + - CGO_ENABLED=0 +archives: + - replacements: + darwin: darwin + linux: linux + windows: windows + 386: i386 + amd64: x86_64 + format_overrides: + - goos: windows + format: zip +changelog: + skip: true +release: + draft: true \ No newline at end of file diff --git a/README.md b/README.md index 16dfdc1..61c1a37 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ It wraps, pipes and extends AWS API. # Install -Install source code with go, build the binaries yourself, and run as `abc` command. +You can install binaries from [releases](https://github.com/Blue-Pix/abc/releases), +or pull [repository](https://github.com/Blue-Pix/abc) and build the binaries yourself. +If you build yourself: **Prerequisition** - Git - Go diff --git a/cmd/ami.go b/cmd/ami.go index 04c40cb..6e6ae73 100644 --- a/cmd/ami.go +++ b/cmd/ami.go @@ -16,185 +16,13 @@ limitations under the License. package cmd import ( - "encoding/json" - "fmt" - "regexp" - "strconv" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/spf13/cobra" + "github.com/Blue-Pix/abc/lib/ami" ) // amiCmd represents the ami command -var amiCmd = &cobra.Command{ - Use: "ami", - Short: "return latest amazon linux ami", - Long: `[abc ami] -This command returns latest amazon linux ami as json format. - -Internally it uses ssm get-parameters-by-path api. (https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-store-public-parameters.html) -Please configure your aws credential which has required policy. - -By default, this returns serveral type of amis. -You can query it with options below. `, - Run: func(cmd *cobra.Command, args []string) { - amis := getAMIList() - if version, err := cmd.Flags().GetString("version"); version != "" && err == nil { - amis = filterByVersion(version, amis) - } - if virtualizationType, err := cmd.Flags().GetString("virtualization-type"); virtualizationType != "" && err == nil { - amis = filterByVirtualizationType(virtualizationType, amis) - } - if arch, err := cmd.Flags().GetString("arch"); arch != "" && err == nil { - amis = filterByArch(arch, amis) - } - if storage, err := cmd.Flags().GetString("storage"); storage != "" && err == nil { - amis = filterByStorage(storage, amis) - } - if minimal, err := cmd.Flags().GetString("minimal"); minimal != "" && err == nil { - amis = filterByMinimal(minimal, amis) - } - str := toJSON(amis) - fmt.Println(str) - }, -} +var amiCmd = ami.NewCmd() func init() { + amiCmd.SetOut(rootCmd.OutOrStdout()) rootCmd.AddCommand(amiCmd) - amiCmd.Flags().StringP("version", "v", "", "os version(1 or 2)") - amiCmd.Flags().StringP("virtualization-type", "V", "", "virtualization type(hvm or pv)") - amiCmd.Flags().StringP("arch", "a", "", "cpu architecture(x86_64 or arm64)") - amiCmd.Flags().StringP("storage", "s", "", "storage type(gp2, ebs or s3)") - amiCmd.Flags().StringP("minimal", "m", "", "if minimal image or not(true or false)") -} - -const PATH = "/aws/service/ami-amazon-linux-latest" - -type AMI struct { - Os string `json:"os"` - Version string `json:"version"` - VirtualizationType string `json:"virtualization_type"` - Arch string `json:"arch"` - Storage string `json:"storage"` - Minimal bool `json:"minimal"` - Id string `json:"id"` - Arn string `json:"arn"` -} - -func getParametersByPath(sess *session.Session, token *string, path string) (*ssm.GetParametersByPathOutput, error) { - service := ssm.New(sess) - params := &ssm.GetParametersByPathInput{ - NextToken: token, - Path: aws.String(path), - Recursive: aws.Bool(true), - } - return service.GetParametersByPath(params) -} - -func toAMI(parameter *ssm.Parameter) AMI { - r := regexp.MustCompile(`^` + PATH + `\/([^\d]+)(\d)?-ami-(minimal\-)?(.+)-(.+)-(.+)$`) - list := r.FindAllStringSubmatch(aws.StringValue(parameter.Name), -1) - ami := AMI{ - Os: list[0][1], - Version: "1", - VirtualizationType: list[0][4], - Arch: list[0][5], - Storage: list[0][6], - Id: aws.StringValue(parameter.Value), - Arn: aws.StringValue(parameter.ARN), - } - if list[0][2] != "" { - ami.Version = list[0][2] - } - if list[0][3] != "" { - ami.Minimal = true - } - return ami -} - -func getAMIList() []AMI { - sess := session.Must(session.NewSession()) - var parameters []*ssm.Parameter - var token *string = nil - - for { - resp, err := getParametersByPath(sess, token, PATH) - if err != nil { - panic(err) - } - parameters = append(parameters, resp.Parameters...) - - if resp.NextToken == nil { - break - } - token = resp.NextToken - } - - var amis []AMI - for _, parameter := range parameters { - amis = append(amis, toAMI(parameter)) - } - return amis -} - -func filterByVersion(version string, amis []AMI) []AMI { - var newAmis []AMI - for _, ami := range amis { - if version == ami.Version { - newAmis = append(newAmis, ami) - } - } - return newAmis -} - -func filterByVirtualizationType(virtualizationType string, amis []AMI) []AMI { - var newAmis []AMI - for _, ami := range amis { - if virtualizationType == ami.VirtualizationType { - newAmis = append(newAmis, ami) - } - } - return newAmis -} - -func filterByArch(arch string, amis []AMI) []AMI { - var newAmis []AMI - for _, ami := range amis { - if arch == ami.Arch { - newAmis = append(newAmis, ami) - } - } - return newAmis -} - -func filterByStorage(storage string, amis []AMI) []AMI { - var newAmis []AMI - for _, ami := range amis { - if storage == ami.Storage { - newAmis = append(newAmis, ami) - } - } - return newAmis -} - -func filterByMinimal(minimal string, amis []AMI) []AMI { - var newAmis []AMI - for _, ami := range amis { - m, _ := strconv.ParseBool(minimal) - if m == ami.Minimal { - newAmis = append(newAmis, ami) - } - } - return newAmis -} - -func toJSON(amis []AMI) string { - jsonBytes, err := json.Marshal(amis) - if err != nil { - panic(err) - } - jsonStr := string(jsonBytes) - return jsonStr } diff --git a/cmd/root.go b/cmd/root.go index 9b44299..378f847 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,9 +17,11 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "os" + "github.com/Blue-Pix/abc/lib/root" + "github.com/spf13/cobra" + homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" ) @@ -27,14 +29,7 @@ import ( var cfgFile string // rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "abc", - Short: "helper command to become friends with AWS🤝", - Long: `A usage, please read each sub commands`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, -} +var rootCmd = root.NewCmd() // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. diff --git a/go.mod b/go.mod index 297a6f3..f63e177 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,8 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.4.0 + golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect + golang.org/x/text v0.3.2 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 2f0839c..f15da8c 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -17,7 +18,9 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -43,6 +46,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -52,8 +56,10 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -67,7 +73,9 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -99,6 +107,7 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y 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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= @@ -117,6 +126,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -128,10 +138,15 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -141,9 +156,13 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lib/ami/ami.go b/lib/ami/ami.go new file mode 100644 index 0000000..2f03bb6 --- /dev/null +++ b/lib/ami/ami.go @@ -0,0 +1,191 @@ +package ami + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/spf13/cobra" +) + +const PATH = "/aws/service/ami-amazon-linux-latest" + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "ami", + Short: "return latest amazon linux ami", + Long: `[abc ami] + This command returns latest amazon linux ami as json format. + + Internally it uses ssm get-parameters-by-path api. (https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-store-public-parameters.html) + Please configure your aws credential which has required policy. + + By default, this returns serveral type of amis. + You can query it with options below. `, + Run: run, + } + cmd.Flags().StringP("version", "v", "", "os version(1 or 2)") + cmd.Flags().StringP("virtualization-type", "V", "", "virtualization type(hvm or pv)") + cmd.Flags().StringP("arch", "a", "", "cpu architecture(x86_64 or arm64)") + cmd.Flags().StringP("storage", "s", "", "storage type(gp2, ebs or s3)") + cmd.Flags().StringP("minimal", "m", "", "if minimal image or not(true or false)") + return cmd +} + +func run(cmd *cobra.Command, args []string) { + str := Run(cmd, args) + cmd.Println(str) +} + +func Run(cmd *cobra.Command, args []string) string { + amis := getAMIList() + if version, err := cmd.Flags().GetString("version"); version != "" && err == nil { + amis = filterByVersion(version, amis) + } + if virtualizationType, err := cmd.Flags().GetString("virtualization-type"); virtualizationType != "" && err == nil { + amis = filterByVirtualizationType(virtualizationType, amis) + } + if arch, err := cmd.Flags().GetString("arch"); arch != "" && err == nil { + amis = filterByArch(arch, amis) + } + if storage, err := cmd.Flags().GetString("storage"); storage != "" && err == nil { + amis = filterByStorage(storage, amis) + } + if minimal, err := cmd.Flags().GetString("minimal"); minimal != "" && err == nil { + amis = filterByMinimal(minimal, amis) + } + str := toJSON(amis) + return str +} + +type AMI struct { + Os string `json:"os"` + Version string `json:"version"` + VirtualizationType string `json:"virtualization_type"` + Arch string `json:"arch"` + Storage string `json:"storage"` + Minimal bool `json:"minimal"` + Id string `json:"id"` + Arn string `json:"arn"` +} + +func getParametersByPath(sess *session.Session, token *string, path string) (*ssm.GetParametersByPathOutput, error) { + service := ssm.New(sess) + params := &ssm.GetParametersByPathInput{ + NextToken: token, + Path: aws.String(path), + Recursive: aws.Bool(true), + } + return service.GetParametersByPath(params) +} + +func toAMI(parameter *ssm.Parameter) AMI { + r := regexp.MustCompile(`^` + PATH + `\/([^\d]+)(\d)?-ami-(minimal\-)?(.+)-(.+)-(.+)$`) + list := r.FindAllStringSubmatch(aws.StringValue(parameter.Name), -1) + ami := AMI{ + Os: list[0][1], + Version: "1", + VirtualizationType: list[0][4], + Arch: list[0][5], + Storage: list[0][6], + Id: aws.StringValue(parameter.Value), + Arn: aws.StringValue(parameter.ARN), + } + if list[0][2] != "" { + ami.Version = list[0][2] + } + if list[0][3] != "" { + ami.Minimal = true + } + return ami +} + +func getAMIList() []AMI { + sess := session.Must(session.NewSession()) + var parameters []*ssm.Parameter + var token *string = nil + + for { + resp, err := getParametersByPath(sess, token, PATH) + if err != nil { + panic(err) + } + parameters = append(parameters, resp.Parameters...) + + if resp.NextToken == nil { + break + } + token = resp.NextToken + } + + var amis []AMI + for _, parameter := range parameters { + amis = append(amis, toAMI(parameter)) + } + return amis +} + +func filterByVersion(version string, amis []AMI) []AMI { + var newAmis []AMI + for _, ami := range amis { + if version == ami.Version { + newAmis = append(newAmis, ami) + } + } + return newAmis +} + +func filterByVirtualizationType(virtualizationType string, amis []AMI) []AMI { + var newAmis []AMI + for _, ami := range amis { + if virtualizationType == ami.VirtualizationType { + newAmis = append(newAmis, ami) + } + } + return newAmis +} + +func filterByArch(arch string, amis []AMI) []AMI { + var newAmis []AMI + for _, ami := range amis { + if arch == ami.Arch { + newAmis = append(newAmis, ami) + } + } + return newAmis +} + +func filterByStorage(storage string, amis []AMI) []AMI { + var newAmis []AMI + for _, ami := range amis { + if storage == ami.Storage { + newAmis = append(newAmis, ami) + } + } + return newAmis +} + +func filterByMinimal(minimal string, amis []AMI) []AMI { + var newAmis []AMI + for _, ami := range amis { + m, _ := strconv.ParseBool(minimal) + if m == ami.Minimal { + newAmis = append(newAmis, ami) + } + } + return newAmis +} + +func toJSON(amis []AMI) string { + jsonBytes, err := json.Marshal(amis) + if err != nil { + panic(err) + fmt.Println(err) + } + jsonStr := string(jsonBytes) + return jsonStr +} diff --git a/lib/root/root.go b/lib/root/root.go new file mode 100644 index 0000000..de8628b --- /dev/null +++ b/lib/root/root.go @@ -0,0 +1,18 @@ +package root + +import ( + "github.com/spf13/cobra" +) + +func NewCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "abc", + Short: "helper command to become friends with AWS🤝", + Long: `A usage, please read each sub commands`, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Help() + return nil + }, + } + return cmd +} diff --git a/lib/root/root_test.go b/lib/root/root_test.go new file mode 100644 index 0000000..931bea2 --- /dev/null +++ b/lib/root/root_test.go @@ -0,0 +1,278 @@ +package root + +import ( + "bytes" + "fmt" + "io/ioutil" + "regexp" + "testing" + + "github.com/Blue-Pix/abc/lib/ami" + "github.com/spf13/cobra" +) + +func prepareCmd(args []string) *cobra.Command { + cmd := NewCmd() + cmd.SetArgs(args) + amiCmd := ami.NewCmd() + cmd.AddCommand(amiCmd) + return cmd +} + +func TestExecute(t *testing.T) { + t.Run("ami", func(t *testing.T) { + t.Run("query by --version", func(t *testing.T) { + args := []string{"ami", "--version", "2"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"version\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by -v", func(t *testing.T) { + args := []string{"ami", "-v", "1"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"version\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by --virtualization-type", func(t *testing.T) { + args := []string{"ami", "--virtualization-type", "hvm"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"virtualization_type\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by -V", func(t *testing.T) { + args := []string{"ami", "-V", "pv"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"virtualization_type\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by --arch", func(t *testing.T) { + args := []string{"ami", "--arch", "x86_64"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"arch\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by -a", func(t *testing.T) { + args := []string{"ami", "-a", "arm64"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"arch\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by --storage", func(t *testing.T) { + args := []string{"ami", "--storage", "gp2"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"storage\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by -s", func(t *testing.T) { + args := []string{"ami", "-s", "ebs"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"storage\":\"([^\"]+)\"") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by --minimal", func(t *testing.T) { + args := []string{"ami", "--minimal", "true"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"minimal\":([^\",]+),") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query by -m", func(t *testing.T) { + args := []string{"ami", "-m", "false"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"minimal\":([^\",]+),") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + } + }) + + t.Run("query with full options", func(t *testing.T) { + args := []string{"ami", "-v", "2", "-V", "hvm", "-a", "x86_64", "-s", "gp2", "-m", "false"} + cmd := prepareCmd(args) + b := bytes.NewBufferString("") + cmd.SetOut(b) + cmd.Execute() + out, err := ioutil.ReadAll(b) + if err != nil { + t.Fatal(err) + } + r := regexp.MustCompile("\"version\":\"([^\"]+)\",\"virtualization_type\":\"([^\"]+)\",\"arch\":\"([^\"]+)\",\"storage\":\"([^\"]+)\",\"minimal\":([^\",]+),") + list := r.FindAllStringSubmatch(string(out), -1) + if len(list) == 0 { + t.Fatal("there is no much result") + } + for _, l := range list { + if l[1] != args[2] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[2], l[1])) + } + if l[2] != args[4] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[4], l[2])) + } + if l[3] != args[6] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[6], l[3])) + } + if l[4] != args[8] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[8], l[4])) + } + if l[5] != args[10] { + t.Fatal(fmt.Sprintf("expected: %s, actual: %s", args[10], l[5])) + } + } + }) + }) +} diff --git a/main.go b/main.go index db8af3b..23ef81e 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,10 @@ package main import "github.com/Blue-Pix/abc/cmd" +var ( + Version = "0.1.1" +) + func main() { cmd.Execute() }