Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
kichik committed Jan 3, 2022
0 parents commit 1e8716f
Show file tree
Hide file tree
Showing 13 changed files with 1,238 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: goreleaser

on:
pull_request:
push:

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: snapshot
path: dist/*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
.idea/
40 changes: 40 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
project_name: cloud-z
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
goarch:
- amd64
- arm64
ldflags: -s -w -X cloud-z/cmd.version={{.Version}} -X cloud-z/cmd.commit={{.Commit}} -X cloud-z/cmd.date={{.Date}} -X cloud-z/cmd.builtBy=goreleaser
archives:
- replacements:
linux: Linux
windows: Windows
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
use: github
sort: asc
groups:
- title: Features
regexp: "^.*feat[(\\w)]*:+.*$"
order: 0
- title: 'Bug fixes'
regexp: "^.*fix[(\\w)]*:+.*$"
order: 1
- title: Others
order: 999
filters:
exclude:
- '^docs:'
- '^test:'
- '^ci:'
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Cloud-Z

Cloud-Z gathers information and perform benchmarks on cloud instances in multiple cloud providers.

- [x] Cloud type, instance id, and type
- [ ] CPU information including type, number of available cores, and cache sizes
- [ ] Benchmark CPU
- [ ] Storage devices information
- [ ] Benchmark storage
- [ ] Network devices information
- [ ] Benchmark network

### Supported clouds:

* Amazon Web Services (AWS)
* Google Cloud Platform (GCP)
* Microsoft Azure

### Supported platforms:

* Windows
* x86_64
* arm64
* Linux
* x86_64
* arm64

[![CI](https://github.com/CloudSnorkel/cloud-z/actions/workflows/goreleaser.yml/badge.svg)](https://github.com/CloudSnorkel/cloud-z/actions/workflows/goreleaser.yml)

## Usage

Cloud-Z is provided as a single binary that can be downloaded from the [releases page](https://github.com/CloudSnorkel/cloud-z/releases).

```bash
$ ./cloud-z
+---------------+-----------------------+
| Cloud | AWS |
| AMI | ami-0712c70d31ba14f8a |
| Instance ID | i-12345678900112344 |
| Instance type | t4g.nano |
+---------------+-----------------------+
```
57 changes: 57 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"cloud-z/providers"
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"log"
"os"
)

var (
version = "dev"
commit = "none"
date = "unknown"
builtBy = "unknown"
)

var rootCmd = &cobra.Command{
Use: "cloud-z",
Short: "Cloud-Z gathers information on cloud instances",
Version: fmt.Sprintf("%s, commit %s, built at %s by %s", version, commit, date, builtBy),
Run: func(cmd *cobra.Command, args []string) {
allProviders := []providers.Provider{
&providers.AwsProvider{},
&providers.GcpProvider{},
&providers.AzureProvider{},
}

for _, provider := range allProviders {
// TODO detect faster with goroutines?
if provider.Detect() {
data, err := provider.GetData()
if err != nil {
log.Fatalln(err)
}

table := tablewriter.NewWriter(os.Stdout)
for _, v := range data {
table.Append(v)
}
table.Render()

return
}
}

println("Unable to detect cloud provider")
},
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module cloud-z

go 1.17

require (
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.3.0
)

require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
765 changes: 765 additions & 0 deletions go.sum

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import (
"cloud-z/cmd"
)

func main() {
cmd.Execute()
}
90 changes: 90 additions & 0 deletions metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package metadata

import (
"encoding/json"
"errors"
"io"
"net/http"
)

func GetMetadataHeader(header string) (string, error) {
resp, err := http.Get("http://169.254.169.254/")

if err != nil {
return "", err
}

resp.Body.Close()

return resp.Header.Get(header), nil
}

var UnauthorizedError = errors.New("metadata server returned 401")

func requestMetadata(action string, url string, headerName string, headerValue string) (*http.Response, error) {
req, err := http.NewRequest(action, "http://169.254.169.254"+url, nil)
if err != nil {
return nil, err
}

if headerName != "" && headerValue != "" {
req.Header.Add(headerName, headerValue)
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return resp, err
}

if resp.StatusCode == 401 {
defer resp.Body.Close()
return resp, UnauthorizedError
} else if resp.StatusCode != 200 {
defer resp.Body.Close()
return resp, errors.New(resp.Status)
}

return resp, nil
}

func GetMetadataJson(url string, target interface{}, headerName string, headerValue string) error {
resp, err := requestMetadata("GET", url, headerName, headerValue)
if err != nil {
return err
}

defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(target)
}

func GetMetadataText(url string, headerName string, headerValue string) (string, error) {
resp, err := requestMetadata("GET", url, headerName, headerValue)
if err != nil {
return "", err
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

return string(body), nil
}

func PutMetadata(url string, headerName string, headerValue string) (string, error) {
resp, err := requestMetadata("PUT", url, headerName, headerValue)
if err != nil {
return "", err
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

return string(body), nil
}
88 changes: 88 additions & 0 deletions providers/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package providers

import (
"cloud-z/metadata"
"errors"
"log"
)

type AwsProvider struct {
token *string
instanceIdentityDocument *instanceIdentityDocumentType
}

func (provider *AwsProvider) Detect() bool {
server, err := metadata.GetMetadataHeader("Server")

if err != nil {
log.Println(err)
}

return server == "EC2ws"
}

func (provider *AwsProvider) getMetadataWithPossibleToken(url string, target interface{}) error {
if provider.token != nil {
return metadata.GetMetadataJson(url, target, "X-aws-ec2-metadata-token", *provider.token)
}

err := metadata.GetMetadataJson(url, target, "", "")
if errors.Is(err, metadata.UnauthorizedError) {
tokenValue, err := metadata.PutMetadata("/latest/api/token", "X-aws-ec2-metadata-token-ttl-seconds", "120")
if err != nil {
return err
}
provider.token = &tokenValue
} else {
return err
}

return metadata.GetMetadataJson(url, target, "X-aws-ec2-metadata-token", *provider.token)
}

type instanceIdentityDocumentType struct {
MarketplaceProductCodes *[]string `json:"marketplaceProductCodes"`
AvailabilityZone string `json:"availabilityZone"`
PrivateIp string `json:"privateIp"`
Version string `json:"version"`
InstanceId string `json:"instanceId"`
BillingProducts *[]string `json:"billingProducts"`
InstanceType string `json:"instanceType"`
AccountId string `json:"accountId"`
ImageId string `json:"imageId"`
PendingTime string `json:"pendingTime"`
Architecture string `json:"architecture"`
KernelId *string `json:"kernelId"`
RamdiskId *string `json:"ramdiskId"`
Region string `json:"region"`
}

func (provider *AwsProvider) getInstanceIdentity() error {
if provider.instanceIdentityDocument != nil {
return nil
}

provider.instanceIdentityDocument = &instanceIdentityDocumentType{}
err := provider.getMetadataWithPossibleToken("/2021-07-15/dynamic/instance-identity/document", provider.instanceIdentityDocument)
if err != nil {
provider.instanceIdentityDocument = nil
return err
}

return nil
}

func (provider *AwsProvider) GetData() ([][]string, error) {
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
err := provider.getInstanceIdentity()
if err != nil {
return [][]string{}, err
}

return [][]string{
{"Cloud", "AWS"},
{"AMI", provider.instanceIdentityDocument.ImageId},
{"Instance ID", provider.instanceIdentityDocument.InstanceId},
{"Instance type", provider.instanceIdentityDocument.InstanceType},
}, nil
}
Loading

0 comments on commit 1e8716f

Please sign in to comment.