From d0f94be1e10e03a9e00d07c8c4a08eefe0dc2ceb Mon Sep 17 00:00:00 2001 From: bt353 Date: Fri, 31 Jan 2025 18:29:26 -0500 Subject: [PATCH 1/2] Init commit for resourcegroups --- .gitignore | 3 +- Makefile | 109 +++++++++++++++++++++++++ api/handlers_resourcegroups.go | 1 + api/orchestration_snapshot.go | 1 - api/routes.go | 5 ++ go.mod | 14 ++++ go.sum | 60 ++++++++++++++ main.go | 24 +++++- resourcegroups/resourcegroups.go | 51 ++++++++++++ resourcegroups/resourcegroups_test.go | 111 ++++++++++++++++++++++++++ 10 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 Makefile create mode 100644 api/handlers_resourcegroups.go create mode 100644 resourcegroups/resourcegroups.go create mode 100644 resourcegroups/resourcegroups_test.go diff --git a/.gitignore b/.gitignore index 0880449..58772d8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ deco.json decofile-prod.json docker-compose.yml # Fresh -tmp/runner-build \ No newline at end of file +tmp/runner-build +./docs/* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c9a5ec --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +.PHONY: all build run test clean lint dev docker-build docker-run help + +# Go related variables +BINARY_NAME=ec2apigo +MAIN_FILE=main.go +BUILD_DIR=./tmp + +# Docker related variables +DOCKER_IMAGE=ec2apigo +DOCKER_TAG=latest + +# Go build flags +LDFLAGS=-ldflags "-w -s" + +# Default target +all: build + +# Build the application +build: + @echo "Building $(BINARY_NAME)..." + @go build -o $(BUILD_DIR)/$(BINARY_NAME) $(LDFLAGS) $(MAIN_FILE) + +# Run the application +run: + @go run $(MAIN_FILE) + +# Run with live reload using air +dev: + @echo "Starting development server with air..." + @air + +# Run tests +test: + @echo "Running tests..." + @go test -v ./... + +# Run tests with coverage +test-coverage: + @echo "Running tests with coverage..." + @go test -v -cover ./... + +# Clean build artifacts +clean: + @echo "Cleaning..." + @rm -rf $(BUILD_DIR) + @go clean + @echo "Cleaned build cache" + +# Run go fmt +fmt: + @echo "Running go fmt..." + @go fmt ./... + +# Run go vet +vet: + @echo "Running go vet..." + @go vet ./... + +# Install dependencies +deps: + @echo "Installing dependencies..." + @go mod download + @go mod tidy + +# Run linter +lint: + @echo "Running linter..." + @if command -v golangci-lint >/dev/null; then \ + golangci-lint run; \ + else \ + echo "golangci-lint is not installed. Installing..."; \ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \ + golangci-lint run; \ + fi + +# Build Docker image +docker-build: + @echo "Building Docker image..." + @docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) -f docker/Dockerfile . + +# Run Docker container +docker-run: + @echo "Running Docker container..." + @docker run -p 8080:8080 $(DOCKER_IMAGE):$(DOCKER_TAG) + +# Install development tools +tools: + @echo "Installing development tools..." + @go install github.com/air-verse/air@latest + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +# Display help information +help: + @echo "Available targets:" + @echo " all - Build the application (default)" + @echo " build - Build the application" + @echo " run - Run the application" + @echo " dev - Run the application with live reload using air" + @echo " test - Run tests" + @echo " test-coverage - Run tests with coverage" + @echo " clean - Clean build artifacts" + @echo " fmt - Run go fmt" + @echo " vet - Run go vet" + @echo " deps - Install dependencies" + @echo " lint - Run linter" + @echo " tools - Install development tools" + @echo " docker-build - Build Docker image" + @echo " docker-run - Run Docker container" + @echo " help - Display this help message" \ No newline at end of file diff --git a/api/handlers_resourcegroups.go b/api/handlers_resourcegroups.go new file mode 100644 index 0000000..778f64e --- /dev/null +++ b/api/handlers_resourcegroups.go @@ -0,0 +1 @@ +package api diff --git a/api/orchestration_snapshot.go b/api/orchestration_snapshot.go index d4d7248..a55de14 100644 --- a/api/orchestration_snapshot.go +++ b/api/orchestration_snapshot.go @@ -87,4 +87,3 @@ func (o *ec2Orchestrator) listSnapshots(ctx context.Context, perPage int64, page return out.Snapshots, out.NextToken, nil } - diff --git a/api/routes.go b/api/routes.go index 493ea27..48e0bd5 100644 --- a/api/routes.go +++ b/api/routes.go @@ -50,6 +50,8 @@ func (s *server) routes() { api.HandleFunc("/{account}/volumes/{id}", s.VolumeGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/volumes/{id}/modifications", s.VolumeListModificationsHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/volumes/{id}/snapshots", s.VolumeListSnapshotsHandler).Methods(http.MethodGet) + //api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsListHandler).Methods(http.MethodGet) + //api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/snapshots", s.SnapshotListHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/snapshots/synctags", s.SnapshotSyncTagHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/snapshots/{id}", s.SnapshotGetHandler).Methods(http.MethodGet) @@ -64,6 +66,7 @@ func (s *server) routes() { api.HandleFunc("/{account}/instances", s.InstanceCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/instances/{id}/volumes", s.VolumeAttachHandler).Methods(http.MethodPost) + //api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/sgs", s.SecurityGroupCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/ssm/association", s.SSMAssociationByTagHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/volumes", s.VolumeCreateHandler).Methods(http.MethodPost) @@ -77,6 +80,7 @@ func (s *server) routes() { api.HandleFunc("/{account}/instances/{id}/ssm/association", s.InstanceSSMAssociationHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/instances/{id}/tags", s.InstanceUpdateHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/instances/{id}/attribute", s.InstanceUpdateHandler).Methods(http.MethodPut) + //api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsUpdateHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/sgs/{id}", s.SecurityGroupUpdateHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/sgs/{id}/tags", s.SecurityGroupUpdateHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/volumes/{id}", s.VolumeUpdateHandler).Methods(http.MethodPut) @@ -87,6 +91,7 @@ func (s *server) routes() { api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileCopyHandler).Methods(http.MethodPost) + //api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/sgs/{id}", s.SecurityGroupDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/volumes/{id}", s.VolumeDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/snapshots/{id}", s.SnapshotDeleteHandler).Methods(http.MethodDelete) diff --git a/go.mod b/go.mod index 8592e8d..15d9a0e 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,14 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.5 github.com/sirupsen/logrus v1.9.3 + github.com/swaggo/swag v1.16.4 golang.org/x/crypto v0.31.0 ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -29,10 +33,16 @@ require ( github.com/containerd/console v1.0.3 // indirect github.com/evertras/bubble-table v0.15.2 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -48,7 +58,11 @@ require ( github.com/rivo/uniseg v0.4.4 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index c61a54c..6fea1ba 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/YaleSpinup/apierror v0.1.5 h1:ZW59fFo+bO30GpQ3TvGNBlY7kdX8L9KIEkaztsKnwiY= github.com/YaleSpinup/apierror v0.1.5/go.mod h1:u2smW7kQNefbVbBpNNvHQj9TjLZdVC+fKxwKNtLIhb4= github.com/YaleSpinup/aws-go v0.2.7 h1:IOV77Y7IF4DaRCOCtsdJMDjyIbPP3MawZRbedNk5YXQ= @@ -26,6 +32,7 @@ github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87ini github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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= @@ -33,6 +40,16 @@ github.com/evertras/bubble-table v0.15.2 h1:hVj27V9tk5TD5p6mVv0RK/KJu2sHq0U+mBMu github.com/evertras/bubble-table v0.15.2/go.mod h1:SPOZKbIpyYWPHBNki3fyNpiPBQkvkULAtOT7NTD5fKY= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -47,12 +64,25 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -78,6 +108,7 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -98,19 +129,34 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -119,16 +165,30 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/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= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index db2632c..90f9664 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,23 @@ var ( version = flag.Bool("version", false, "Display version information and exit.") ) +// @title Spinup - EC2 API +// @version 0.29.4 +// @description This is a swagger implementation for the Spinup EC2 API. + +// @contact.name Brandon Tassone +// @contact.url https://yale.service-now.com/ +// @contact.email cloudeng@yale.edu + +// @host localhost:8180 +// @BasePath /v2/ec2 + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name X-Auth-Token +// @description Hashed token authentication that gets passed along with the request. + +// @Security ApiKeyAuth func main() { flag.Parse() if *version { @@ -83,7 +100,12 @@ func main() { if config.LogLevel == "debug" { log.Debug("Starting profiler on 127.0.0.1:6080") - go http.ListenAndServe("127.0.0.1:6080", nil) + go func() { + err := http.ListenAndServe("127.0.0.1:6080", nil) + if err != nil { + + } + }() } log.Debugf("loaded configuration: %+v", config) diff --git a/resourcegroups/resourcegroups.go b/resourcegroups/resourcegroups.go new file mode 100644 index 0000000..535120a --- /dev/null +++ b/resourcegroups/resourcegroups.go @@ -0,0 +1,51 @@ +package resourcegroups + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/aws/aws-sdk-go/service/resourcegroups/resourcegroupsiface" + log "github.com/sirupsen/logrus" +) + +// ResourceGroups is a wrapper around the aws Resource Groups service +type ResourceGroups struct { + session *session.Session + Service resourcegroupsiface.ResourceGroupsAPI +} + +type Option func(*ResourceGroups) + +// New creates a new ResourceGroups +func New(opts ...Option) *ResourceGroups { + rg := ResourceGroups{} + + for _, opt := range opts { + opt(&rg) + } + + if rg.session != nil { + rg.Service = resourcegroups.New(rg.session) + } + + return &rg +} + +func WithSession(sess *session.Session) Option { + return func(rg *ResourceGroups) { + log.Debug("using aws session") + rg.session = sess + } +} + +func WithCredentials(key, secret, token, region string) Option { + return func(rg *ResourceGroups) { + log.Debugf("creating new session with key id %s in region %s", key, region) + sess := session.Must(session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(key, secret, token), + Region: aws.String(region), + })) + rg.session = sess + } +} diff --git a/resourcegroups/resourcegroups_test.go b/resourcegroups/resourcegroups_test.go new file mode 100644 index 0000000..a850f66 --- /dev/null +++ b/resourcegroups/resourcegroups_test.go @@ -0,0 +1,111 @@ +package resourcegroups + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/session" +) + +func TestWithSession(t *testing.T) { + sess := session.Must(session.NewSession()) + rg := New(WithSession(sess)) + + if rg.session != sess { + t.Error("WithSession option did not set the session correctly") + } + + if rg.Service == nil { + t.Error("Service was not initialized with session") + } +} + +func TestWithCredentials(t *testing.T) { + cases := []struct { + name string + key string + secret string + token string + region string + }{ + { + name: "with all credentials", + key: "test-key", + secret: "test-secret", + token: "test-token", + region: "us-east-1", + }, + { + name: "without token", + key: "test-key", + secret: "test-secret", + token: "", + region: "us-west-2", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rg := New(WithCredentials(tc.key, tc.secret, tc.token, tc.region)) + + if rg.session == nil { + t.Error("session was not created") + } + + if rg.Service == nil { + t.Error("Service was not initialized") + } + }) + } +} + +func TestNewWithMultipleOptions(t *testing.T) { + cases := []struct { + name string + opts []Option + wantNil bool + }{ + { + name: "no options", + opts: []Option{}, + wantNil: true, + }, + { + name: "with credentials", + opts: []Option{ + WithCredentials("key", "secret", "", "us-east-1"), + }, + wantNil: false, + }, + { + name: "with session", + opts: []Option{ + WithSession(session.Must(session.NewSession())), + }, + wantNil: false, + }, + { + name: "with both options", + opts: []Option{ + WithSession(session.Must(session.NewSession())), + WithCredentials("key", "secret", "", "us-east-1"), + }, + wantNil: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rg := New(tc.opts...) + + if tc.wantNil { + if rg.Service != nil { + t.Error("Service should be nil when no options are provided") + } + } else { + if rg.Service == nil { + t.Error("Service should not be nil when options are provided") + } + } + }) + } +} From 8ccfa17179efd7aa95bf6bd930ba6eee3e4bb233 Mon Sep 17 00:00:00 2001 From: bt353 Date: Mon, 3 Feb 2025 07:50:48 -0500 Subject: [PATCH 2/2] Init commit for resourcegroups --- .github/workflows/test.yaml | 2 +- api/handlers.go | 2 +- api/handlers_resourcegroups.go | 179 ++++++++ api/orchestration_resourcegroups.go | 84 ++++ api/routes.go | 8 +- api/server.go | 21 +- docker/Dockerfile | 2 +- docker/Dockerfile.local | 2 +- go.mod | 56 +-- go.sum | 279 ++++++------ main.go | 24 +- resourcegroups/resourcegroups.go | 114 +++++ resourcegroups/resourcegroups_test.go | 601 ++++++++++++++++++++++++++ 13 files changed, 1166 insertions(+), 208 deletions(-) create mode 100644 api/orchestration_resourcegroups.go diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c4401fa..4891f84 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ jobs: strategy: matrix: go-version: - - "1.21.x" + - "1.23.x" os: - "ubuntu-latest" steps: diff --git a/api/handlers.go b/api/handlers.go index 608db70..495a339 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -25,7 +25,7 @@ import ( log "github.com/sirupsen/logrus" ) -// PingHandler responds to ping requests +// PingHandler Ping Returns a response of pong if the application is up and running func (s *server) PingHandler(w http.ResponseWriter, r *http.Request) { w = LogWriter{w} log.Debug("Ping/Pong") diff --git a/api/handlers_resourcegroups.go b/api/handlers_resourcegroups.go index 778f64e..540d22a 100644 --- a/api/handlers_resourcegroups.go +++ b/api/handlers_resourcegroups.go @@ -1 +1,180 @@ +/* +Copyright © 2021 Yale University + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ package api + +import ( + "encoding/json" + "github.com/gorilla/mux" + log "github.com/sirupsen/logrus" + "net/http" + + "github.com/YaleSpinup/apierror" + rg2 "github.com/YaleSpinup/ec2-api/resourcegroups" + "github.com/aws/aws-sdk-go/service/resourcegroups" +) + +// ResourceGroupsCreateHandler handles the creation of a new resource group +func (s *server) ResourceGroupsCreateHandler(w http.ResponseWriter, r *http.Request) { + w = LogWriter{w} + w.Header().Set("Access-Control-Allow-Origin", "*") + + vars := mux.Vars(r) + account := vars["account"] + + log.Debugf("Creating resource group in account: %s", account) + + // Initialize the resource groups service for this account + if err := s.setResourceGroupsService(account); err != nil { + log.Errorf("Failed to initialize resource groups service: %v", err) + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to initialize resource groups service", err)) + return + } + + // Updated input struct to match AWS SDK expectations + var input struct { + Name string `json:"name"` + Description string `json:"description"` + Query struct { + Type string `json:"Type"` + Query string `json:"Query"` + } `json:"query"` + } + + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "invalid json input", err)) + return + } + + if input.Name == "" { + handleError(w, apierror.New(apierror.ErrBadRequest, "name is required", nil)) + return + } + + // Convert the input to AWS SDK format + createInput := rg2.CreateGroupInput{ + Name: input.Name, + Description: input.Description, + ResourceQuery: &resourcegroups.ResourceQuery{ + Type: &input.Query.Type, + Query: &input.Query.Query, + }, + } + + group, err := s.resourceGroups.CreateGroup(createInput) + if err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to create resource group", err)) + return + } + + handleResponseOk(w, group) +} + +// ResourceGroupsListHandler lists all resource groups +func (s *server) ResourceGroupsListHandler(w http.ResponseWriter, r *http.Request) { + w = LogWriter{w} + w.Header().Set("Access-Control-Allow-Origin", "*") + + vars := mux.Vars(r) + account := vars["account"] + + log.Debugf("Listing resource groups in account: %s", account) + + if err := s.setResourceGroupsService(account); err != nil { + log.Errorf("Failed to initialize resource groups service: %v", err) + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to initialize resource groups service", err)) + return + } + + groups, err := s.resourceGroups.ListGroups() + if err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to list resource groups", err)) + return + } + + log.Infof("Successfully listed resource groups for account: %s", account) + handleResponseOk(w, groups) +} + +// ResourceGroupsGetHandler gets details of a specific resource group and its resources +func (s *server) ResourceGroupsGetHandler(w http.ResponseWriter, r *http.Request) { + w = LogWriter{w} + w.Header().Set("Access-Control-Allow-Origin", "*") + + vars := mux.Vars(r) + account := vars["account"] + groupName := vars["id"] + + log.Debugf("Getting resource group %s in account: %s", groupName, account) + + if err := s.setResourceGroupsService(account); err != nil { + log.Errorf("Failed to initialize resource groups service: %v", err) + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to initialize resource groups service", err)) + return + } + + // Get group details + group, err := s.resourceGroups.GetGroup(groupName) + if err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to get resource group", err)) + return + } + + // Get resources in the group + resources, err := s.resourceGroups.ListGroupResources(groupName) + if err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to list group resources", err)) + return + } + + // Combine group details and resources + response := struct { + Group *resourcegroups.Group `json:"Group"` + Resources interface{} `json:"Resources"` + }{ + Group: group, + Resources: resources, + } + + log.Infof("Successfully retrieved resource group: %s", groupName) + handleResponseOk(w, response) +} + +// ResourceGroupsDeleteHandler handles deletion of a resource group +func (s *server) ResourceGroupsDeleteHandler(w http.ResponseWriter, r *http.Request) { + w = LogWriter{w} + w.Header().Set("Access-Control-Allow-Origin", "*") + + vars := mux.Vars(r) + account := vars["account"] + groupName := vars["id"] + + log.Debugf("Deleting resource group %s in account: %s", groupName, account) + + if err := s.setResourceGroupsService(account); err != nil { + log.Errorf("Failed to initialize resource groups service: %v", err) + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to initialize resource groups service", err)) + return + } + + if err := s.resourceGroups.DeleteGroup(groupName); err != nil { + handleError(w, apierror.New(apierror.ErrBadRequest, "failed to delete resource group", err)) + return + } + + log.Infof("Successfully deleted resource group: %s", groupName) + handleResponseOk(w, nil) +} diff --git a/api/orchestration_resourcegroups.go b/api/orchestration_resourcegroups.go new file mode 100644 index 0000000..30f2c25 --- /dev/null +++ b/api/orchestration_resourcegroups.go @@ -0,0 +1,84 @@ +/* +Copyright © 2021 Yale University + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package api + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/sts" + + "github.com/YaleSpinup/ec2-api/resourcegroups" + log "github.com/sirupsen/logrus" +) + +func (s *server) resourceGroupsServiceForAccount(account string) (*resourcegroups.ResourceGroups, error) { + log.Debugf("getting resourcegroups service for account %s", account) + + accountNumber := s.mapAccountNumber(account) + if accountNumber == "" { + return nil, fmt.Errorf("account number not found for %s", account) + } + + // Construct the role ARN for the target account + roleArn := fmt.Sprintf("arn:aws:iam::%s:role/SpinupPlusXAManagementRoleTst", accountNumber) + log.Debugf("assuming role: %s", roleArn) + + // Use the existing session to create STS client + stsClient := sts.New(s.session.Session) + + // Create the assume role input + assumeRoleInput := &sts.AssumeRoleInput{ + RoleArn: aws.String(roleArn), + RoleSessionName: aws.String("resource-groups-session"), + } + + // Add ExternalId if it exists + if s.session.ExternalID != "" { + assumeRoleInput.ExternalId = aws.String(s.session.ExternalID) + } + + // Assume the role + roleOutput, err := stsClient.AssumeRole(assumeRoleInput) + if err != nil { + return nil, fmt.Errorf("failed to assume role: %v", err) + } + + // Create a new session with the assumed role credentials + assumedSession := session.Must(session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials( + *roleOutput.Credentials.AccessKeyId, + *roleOutput.Credentials.SecretAccessKey, + *roleOutput.Credentials.SessionToken, + ), + Region: aws.String("us-east-1"), // or get this from config + })) + + return resourcegroups.New(resourcegroups.WithSession(assumedSession)), nil +} + +// setResourceGroupsService sets the resourcegroups service in the server +func (s *server) setResourceGroupsService(account string) error { + rg, err := s.resourceGroupsServiceForAccount(account) + if err != nil { + return err + } + + s.resourceGroups = rg + return nil +} diff --git a/api/routes.go b/api/routes.go index 48e0bd5..47d3e41 100644 --- a/api/routes.go +++ b/api/routes.go @@ -50,8 +50,8 @@ func (s *server) routes() { api.HandleFunc("/{account}/volumes/{id}", s.VolumeGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/volumes/{id}/modifications", s.VolumeListModificationsHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/volumes/{id}/snapshots", s.VolumeListSnapshotsHandler).Methods(http.MethodGet) - //api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsListHandler).Methods(http.MethodGet) - //api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsGetHandler).Methods(http.MethodGet) + api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsListHandler).Methods(http.MethodGet) + api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/snapshots", s.SnapshotListHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/snapshots/synctags", s.SnapshotSyncTagHandler).Methods(http.MethodPut) api.HandleFunc("/{account}/snapshots/{id}", s.SnapshotGetHandler).Methods(http.MethodGet) @@ -66,7 +66,7 @@ func (s *server) routes() { api.HandleFunc("/{account}/instances", s.InstanceCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/instances/{id}/volumes", s.VolumeAttachHandler).Methods(http.MethodPost) - //api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsCreateHandler).Methods(http.MethodPost) + api.HandleFunc("/{account}/resourcegroups", s.ResourceGroupsCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/sgs", s.SecurityGroupCreateHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/ssm/association", s.SSMAssociationByTagHandler).Methods(http.MethodPost) api.HandleFunc("/{account}/volumes", s.VolumeCreateHandler).Methods(http.MethodPost) @@ -91,7 +91,7 @@ func (s *server) routes() { api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileGetHandler).Methods(http.MethodGet) api.HandleFunc("/{account}/instanceprofiles/{name}", s.InstanceProfileCopyHandler).Methods(http.MethodPost) - //api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsDeleteHandler).Methods(http.MethodDelete) + api.HandleFunc("/{account}/resourcegroups/{id}", s.ResourceGroupsDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/sgs/{id}", s.SecurityGroupDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/volumes/{id}", s.VolumeDeleteHandler).Methods(http.MethodDelete) api.HandleFunc("/{account}/snapshots/{id}", s.SnapshotDeleteHandler).Methods(http.MethodDelete) diff --git a/api/server.go b/api/server.go index 35bf963..fdb0e7b 100644 --- a/api/server.go +++ b/api/server.go @@ -19,6 +19,7 @@ package api import ( "context" "errors" + "github.com/YaleSpinup/ec2-api/resourcegroups" "math/rand" "net/http" "os" @@ -54,15 +55,16 @@ type proxyBackend struct { } type server struct { - router *mux.Router - version *apiVersion - context context.Context - session session.Session - sessionCache *cache.Cache - backend *proxyBackend - accountsMap map[string]string - orgPolicy string - org string + router *mux.Router + version *apiVersion + context context.Context + session session.Session + sessionCache *cache.Cache + backend *proxyBackend + accountsMap map[string]string + orgPolicy string + org string + resourceGroups *resourcegroups.ResourceGroups } // NewServer creates a new server and starts it @@ -118,7 +120,6 @@ func NewServer(config common.Config) error { "/v2/ec2/version": "public", "/v2/ec2/metrics": "public", } - // load routes s.routes() diff --git a/docker/Dockerfile b/docker/Dockerfile index 9f6d7c4..0243009 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # build stage -FROM golang:alpine3.18 AS build-env +FROM golang:alpine3.21 AS build-env ARG version="0.0.0" ARG prerelease="" diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local index d7d11d9..48985fa 100644 --- a/docker/Dockerfile.local +++ b/docker/Dockerfile.local @@ -1,5 +1,5 @@ # build stage -FROM golang:alpine3.18 AS build-env +FROM golang:alpine3.21 AS build-env RUN apk add --no-cache git openssh-client gcc musl-dev RUN mkdir /app WORKDIR /app diff --git a/go.mod b/go.mod index 15d9a0e..f8018ba 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/YaleSpinup/ec2-api -go 1.21 +go 1.23 require ( github.com/YaleSpinup/apierror v0.1.5 github.com/YaleSpinup/aws-go v0.2.7 - github.com/aws/amazon-ec2-instance-selector/v2 v2.4.1 - github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/amazon-ec2-instance-selector/v2 v2.0.3 + github.com/aws/aws-sdk-go v1.55.6 github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 @@ -14,55 +14,25 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.20.5 github.com/sirupsen/logrus v1.9.3 - github.com/swaggo/swag v1.16.4 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.32.0 ) require ( - github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charmbracelet/bubbles v0.13.0 // indirect - github.com/charmbracelet/bubbletea v0.21.0 // indirect - github.com/charmbracelet/lipgloss v0.6.0 // indirect - github.com/containerd/console v1.0.3 // indirect - github.com/evertras/bubble-table v0.15.2 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/swag v0.19.15 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/kr/text v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rivo/uniseg v0.4.4 // indirect - github.com/sahilm/fuzzy v0.1.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/protobuf v1.34.2 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + golang.org/x/sys v0.29.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6fea1ba..ad37dd2 100644 --- a/go.sum +++ b/go.sum @@ -1,73 +1,90 @@ -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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/YaleSpinup/apierror v0.1.5 h1:ZW59fFo+bO30GpQ3TvGNBlY7kdX8L9KIEkaztsKnwiY= github.com/YaleSpinup/apierror v0.1.5/go.mod h1:u2smW7kQNefbVbBpNNvHQj9TjLZdVC+fKxwKNtLIhb4= github.com/YaleSpinup/aws-go v0.2.7 h1:IOV77Y7IF4DaRCOCtsdJMDjyIbPP3MawZRbedNk5YXQ= github.com/YaleSpinup/aws-go v0.2.7/go.mod h1:O5e9MepqWMV7fmRPa9E4IIUKaiWCmdHWsSSFmKKvq7Y= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/amazon-ec2-instance-selector/v2 v2.4.1 h1:DmxtwV+pkakkVRhxKcAgnLbxCxvT7k8DBG271dfKPZ8= -github.com/aws/amazon-ec2-instance-selector/v2 v2.4.1/go.mod h1:AEJrtkLkCkfIBIazidrVrgZqaXl+9dxI/wRgjdw+7G0= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/amazon-ec2-instance-selector/v2 v2.0.3 h1:Rf2Wida+XfwS0NRuRLoAcFpmehXzNjEg0kd/PoACFBE= +github.com/aws/amazon-ec2-instance-selector/v2 v2.0.3/go.mod h1:oCl6ho82TvIbrUfTld62nIkRBao22lUKEIMSmpVZDFY= +github.com/aws/aws-sdk-go v1.38.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/bubbles v0.13.0 h1:zP/ROH3wJEBqZWKIsD50ZKKlx3ydLInq3LdD/Nrlb8w= -github.com/charmbracelet/bubbles v0.13.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= -github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= -github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= -github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= -github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= -github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/evertras/bubble-table v0.15.2 h1:hVj27V9tk5TD5p6mVv0RK/KJu2sHq0U+mBMux/HptkU= -github.com/evertras/bubble-table v0.15.2/go.mod h1:SPOZKbIpyYWPHBNki3fyNpiPBQkvkULAtOT7NTD5fKY= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +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/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -77,118 +94,128 @@ 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/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= -github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +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= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= -github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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-20190404232315-eb5bcb51f2a3/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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +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= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/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= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 90f9664..c591672 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "net/http" "os" @@ -46,23 +45,6 @@ var ( version = flag.Bool("version", false, "Display version information and exit.") ) -// @title Spinup - EC2 API -// @version 0.29.4 -// @description This is a swagger implementation for the Spinup EC2 API. - -// @contact.name Brandon Tassone -// @contact.url https://yale.service-now.com/ -// @contact.email cloudeng@yale.edu - -// @host localhost:8180 -// @BasePath /v2/ec2 - -// @securityDefinitions.apikey ApiKeyAuth -// @in header -// @name X-Auth-Token -// @description Hashed token authentication that gets passed along with the request. - -// @Security ApiKeyAuth func main() { flag.Parse() if *version { @@ -134,9 +116,9 @@ func configReader() io.Reader { log.Fatalln("unable to open config file", err) } - c, err := ioutil.ReadAll(configFile) - if err != nil { - log.Fatalln("unable to read config file", err) + c, rErr := io.ReadAll(configFile) + if rErr != nil { + log.Fatalln("unable to read config file", rErr) } return bytes.NewReader(c) diff --git a/resourcegroups/resourcegroups.go b/resourcegroups/resourcegroups.go index 535120a..f10556a 100644 --- a/resourcegroups/resourcegroups.go +++ b/resourcegroups/resourcegroups.go @@ -1,6 +1,8 @@ package resourcegroups import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -15,6 +17,14 @@ type ResourceGroups struct { Service resourcegroupsiface.ResourceGroupsAPI } +// CreateGroupInput represents the input parameters for creating a resource group +type CreateGroupInput struct { + Name string + Description string + ResourceQuery *resourcegroups.ResourceQuery +} + +// Option A resource group option type Option func(*ResourceGroups) // New creates a new ResourceGroups @@ -32,6 +42,7 @@ func New(opts ...Option) *ResourceGroups { return &rg } +// WithSession make sure the connection is using aws session functionality func WithSession(sess *session.Session) Option { return func(rg *ResourceGroups) { log.Debug("using aws session") @@ -39,6 +50,7 @@ func WithSession(sess *session.Session) Option { } } +// WithCredentials configures the resources group aws client with the given credentials func WithCredentials(key, secret, token, region string) Option { return func(rg *ResourceGroups) { log.Debugf("creating new session with key id %s in region %s", key, region) @@ -49,3 +61,105 @@ func WithCredentials(key, secret, token, region string) Option { rg.session = sess } } + +// CreateGroup creates a new AWS resource group with the specified configuration +func (rg *ResourceGroups) CreateGroup(input CreateGroupInput) (*resourcegroups.Group, error) { + if rg.Service == nil { + return nil, fmt.Errorf("resource groups service not initialized") + } + + params := &resourcegroups.CreateGroupInput{ + Name: aws.String(input.Name), + ResourceQuery: input.ResourceQuery, + } + + if input.Description != "" { + params.Description = aws.String(input.Description) + } + + result, err := rg.Service.CreateGroup(params) + if err != nil { + return nil, err + } + + return result.Group, nil +} + +// ListGroups returns all resource groups in the account +func (rg *ResourceGroups) ListGroups() ([]*resourcegroups.Group, error) { + if rg.Service == nil { + return nil, fmt.Errorf("resource groups service not initialized") + } + + input := &resourcegroups.ListGroupsInput{} + var groups []*resourcegroups.Group + + // Handle pagination + err := rg.Service.ListGroupsPages(input, func(page *resourcegroups.ListGroupsOutput, lastPage bool) bool { + groups = append(groups, page.Groups...) + return !lastPage + }) + + if err != nil { + return nil, err + } + + return groups, nil +} + +// GetGroup retrieves details of a specific resource group +func (rg *ResourceGroups) GetGroup(name string) (*resourcegroups.Group, error) { + if rg.Service == nil { + return nil, fmt.Errorf("resource groups service not initialized") + } + + input := &resourcegroups.GetGroupInput{ + GroupName: aws.String(name), + } + + result, err := rg.Service.GetGroup(input) + if err != nil { + return nil, err + } + + return result.Group, nil +} + +// ListGroupResources lists all resources in a specific group +func (rg *ResourceGroups) ListGroupResources(name string) ([]*resourcegroups.ListGroupResourcesItem, error) { + if rg.Service == nil { + return nil, fmt.Errorf("resource groups service not initialized") + } + + input := &resourcegroups.ListGroupResourcesInput{ + GroupName: aws.String(name), + } + + var resources []*resourcegroups.ListGroupResourcesItem + + // Handle pagination + err := rg.Service.ListGroupResourcesPages(input, func(page *resourcegroups.ListGroupResourcesOutput, lastPage bool) bool { + resources = append(resources, page.Resources...) + return !lastPage + }) + + if err != nil { + return nil, err + } + + return resources, nil +} + +// DeleteGroup deletes a resource group +func (rg *ResourceGroups) DeleteGroup(name string) error { + if rg.Service == nil { + return fmt.Errorf("resource groups service not initialized") + } + + input := &resourcegroups.DeleteGroupInput{ + GroupName: aws.String(name), + } + + _, err := rg.Service.DeleteGroup(input) + return err +} diff --git a/resourcegroups/resourcegroups_test.go b/resourcegroups/resourcegroups_test.go index a850f66..56537c3 100644 --- a/resourcegroups/resourcegroups_test.go +++ b/resourcegroups/resourcegroups_test.go @@ -1,11 +1,60 @@ package resourcegroups import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/aws/aws-sdk-go/service/resourcegroups/resourcegroupsiface" "testing" "github.com/aws/aws-sdk-go/aws/session" ) +// MockResourceGroupsClient is a mock implementation of resourcegroupsiface.ResourceGroupsAPI +type MockResourceGroupsClient struct { + resourcegroupsiface.ResourceGroupsAPI + mockCreateGroup func(*resourcegroups.CreateGroupInput) (*resourcegroups.CreateGroupOutput, error) + mockListGroupsPages func(*resourcegroups.ListGroupsInput, func(*resourcegroups.ListGroupsOutput, bool) bool) error + mockGetGroup func(*resourcegroups.GetGroupInput) (*resourcegroups.GetGroupOutput, error) + mockListGroupResourcesPages func(*resourcegroups.ListGroupResourcesInput, func(*resourcegroups.ListGroupResourcesOutput, bool) bool) error + mockDeleteGroup func(*resourcegroups.DeleteGroupInput) (*resourcegroups.DeleteGroupOutput, error) +} + +func (m *MockResourceGroupsClient) CreateGroup(input *resourcegroups.CreateGroupInput) (*resourcegroups.CreateGroupOutput, error) { + if m.mockCreateGroup != nil { + return m.mockCreateGroup(input) + } + return nil, nil +} + +func (m *MockResourceGroupsClient) ListGroupsPages(input *resourcegroups.ListGroupsInput, fn func(*resourcegroups.ListGroupsOutput, bool) bool) error { + if m.mockListGroupsPages != nil { + return m.mockListGroupsPages(input, fn) + } + return nil +} + +func (m *MockResourceGroupsClient) GetGroup(input *resourcegroups.GetGroupInput) (*resourcegroups.GetGroupOutput, error) { + if m.mockGetGroup != nil { + return m.mockGetGroup(input) + } + return nil, nil +} + +func (m *MockResourceGroupsClient) ListGroupResourcesPages(input *resourcegroups.ListGroupResourcesInput, fn func(*resourcegroups.ListGroupResourcesOutput, bool) bool) error { + if m.mockListGroupResourcesPages != nil { + return m.mockListGroupResourcesPages(input, fn) + } + return nil +} + +func (m *MockResourceGroupsClient) DeleteGroup(input *resourcegroups.DeleteGroupInput) (*resourcegroups.DeleteGroupOutput, error) { + if m.mockDeleteGroup != nil { + return m.mockDeleteGroup(input) + } + return nil, nil +} + func TestWithSession(t *testing.T) { sess := session.Must(session.NewSession()) rg := New(WithSession(sess)) @@ -109,3 +158,555 @@ func TestNewWithMultipleOptions(t *testing.T) { }) } } + +func TestCreateGroup(t *testing.T) { + cases := []struct { + name string + input CreateGroupInput + mockResponse *resourcegroups.CreateGroupOutput + mockErr error + expectedError bool + }{ + { + name: "successful creation", + input: CreateGroupInput{ + Name: "test-group", + Description: "test description", + ResourceQuery: &resourcegroups.ResourceQuery{ + Type: aws.String("TAG_FILTERS_1_0"), + Query: aws.String(`{ + "ResourceTypeFilters": ["AWS::EC2::Instance"], + "TagFilters": [ + { + "Key": "Environment", + "Values": ["Test"] + } + ] + }`), + }, + }, + mockResponse: &resourcegroups.CreateGroupOutput{ + Group: &resourcegroups.Group{ + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group"), + Name: aws.String("test-group"), + Description: aws.String("test description"), + }, + }, + expectedError: false, + }, + { + name: "aws error", + input: CreateGroupInput{ + Name: "test-group", + ResourceQuery: &resourcegroups.ResourceQuery{ + Type: aws.String("TAG_FILTERS_1_0"), + Query: aws.String("{}"), + }, + }, + mockErr: fmt.Errorf("AWS error"), + expectedError: true, + }, + { + name: "nil service", + input: CreateGroupInput{ + Name: "test-group", + ResourceQuery: &resourcegroups.ResourceQuery{ + Type: aws.String("TAG_FILTERS_1_0"), + Query: aws.String("{}"), + }, + }, + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mock := &MockResourceGroupsClient{ + mockCreateGroup: func(input *resourcegroups.CreateGroupInput) (*resourcegroups.CreateGroupOutput, error) { + if tc.mockErr != nil { + return nil, tc.mockErr + } + return tc.mockResponse, nil + }, + } + + rg := &ResourceGroups{ + Service: mock, + } + + if tc.name == "nil service" { + rg.Service = nil + } + + group, err := rg.CreateGroup(tc.input) + + if tc.expectedError { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if group == nil { + t.Error("expected group but got nil") + } + } + }) + } +} + +func TestListGroups(t *testing.T) { + cases := []struct { + name string + mockResponse []*resourcegroups.Group + mockErr error + expectedError bool + nilService bool + }{ + { + name: "successful listing - single page", + mockResponse: []*resourcegroups.Group{ + { + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group-1"), + Name: aws.String("test-group-1"), + Description: aws.String("test description 1"), + }, + { + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group-2"), + Name: aws.String("test-group-2"), + Description: aws.String("test description 2"), + }, + }, + expectedError: false, + }, + { + name: "successful listing - multiple pages", + mockResponse: []*resourcegroups.Group{ + { + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group-1"), + Name: aws.String("test-group-1"), + }, + { + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group-2"), + Name: aws.String("test-group-2"), + }, + { + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group-3"), + Name: aws.String("test-group-3"), + }, + }, + expectedError: false, + }, + { + name: "aws error", + mockErr: fmt.Errorf("AWS error"), + expectedError: true, + }, + { + name: "nil service", + nilService: true, + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mock := &MockResourceGroupsClient{ + mockListGroupsPages: func(input *resourcegroups.ListGroupsInput, fn func(*resourcegroups.ListGroupsOutput, bool) bool) error { + if tc.mockErr != nil { + return tc.mockErr + } + + if len(tc.mockResponse) > 2 { + // Simulate pagination for responses with more than 2 items + fn(&resourcegroups.ListGroupsOutput{ + Groups: tc.mockResponse[:2], + }, false) + fn(&resourcegroups.ListGroupsOutput{ + Groups: tc.mockResponse[2:], + }, true) + } else { + fn(&resourcegroups.ListGroupsOutput{ + Groups: tc.mockResponse, + }, true) + } + return nil + }, + } + + rg := &ResourceGroups{ + Service: mock, + } + + if tc.nilService { + rg.Service = nil + } + + groups, err := rg.ListGroups() + + if tc.expectedError { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if groups == nil { + t.Error("expected groups but got nil") + } + if len(groups) != len(tc.mockResponse) { + t.Errorf("expected %d groups but got %d", len(tc.mockResponse), len(groups)) + } + // Verify each group matches the mock response + for i, group := range groups { + if *group.GroupArn != *tc.mockResponse[i].GroupArn { + t.Errorf("expected group ARN %s but got %s", *tc.mockResponse[i].GroupArn, *group.GroupArn) + } + if *group.Name != *tc.mockResponse[i].Name { + t.Errorf("expected group name %s but got %s", *tc.mockResponse[i].Name, *group.Name) + } + } + } + }) + } +} + +func TestGetGroup(t *testing.T) { + cases := []struct { + name string + groupName string + mockResponse *resourcegroups.GetGroupOutput + mockErr error + expectedError bool + nilService bool + }{ + { + name: "successful retrieval", + groupName: "test-group", + mockResponse: &resourcegroups.GetGroupOutput{ + Group: &resourcegroups.Group{ + GroupArn: aws.String("arn:aws:resource-groups:us-east-1:123456789012:group/test-group"), + Name: aws.String("test-group"), + Description: aws.String("test description"), + }, + }, + expectedError: false, + }, + { + name: "group not found", + groupName: "non-existent-group", + mockErr: fmt.Errorf("ResourceNotFoundException: Group not found"), + expectedError: true, + }, + { + name: "aws error", + groupName: "test-group", + mockErr: fmt.Errorf("AWS error"), + expectedError: true, + }, + { + name: "nil service", + groupName: "test-group", + nilService: true, + expectedError: true, + }, + { + name: "empty group name", + groupName: "", + mockErr: fmt.Errorf("ValidationException: Group name is required"), + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mock := &MockResourceGroupsClient{ + mockGetGroup: func(input *resourcegroups.GetGroupInput) (*resourcegroups.GetGroupOutput, error) { + // Verify input + if input.GroupName == nil { + t.Error("GroupName should not be nil") + } else if *input.GroupName != tc.groupName { + t.Errorf("expected group name %s but got %s", tc.groupName, *input.GroupName) + } + + if tc.mockErr != nil { + return nil, tc.mockErr + } + return tc.mockResponse, nil + }, + } + + rg := &ResourceGroups{ + Service: mock, + } + + if tc.nilService { + rg.Service = nil + } + + group, err := rg.GetGroup(tc.groupName) + + if tc.expectedError { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if group == nil { + t.Error("expected group but got nil") + } else { + // Verify group details + expectedGroup := tc.mockResponse.Group + if *group.GroupArn != *expectedGroup.GroupArn { + t.Errorf("expected group ARN %s but got %s", *expectedGroup.GroupArn, *group.GroupArn) + } + if *group.Name != *expectedGroup.Name { + t.Errorf("expected group name %s but got %s", *expectedGroup.Name, *group.Name) + } + if *group.Description != *expectedGroup.Description { + t.Errorf("expected group description %s but got %s", *expectedGroup.Description, *group.Description) + } + } + } + }) + } +} + +func TestListGroupResources(t *testing.T) { + cases := []struct { + name string + groupName string + mockResponse []*resourcegroups.ListGroupResourcesItem + mockErr error + expectedError bool + nilService bool + }{ + { + name: "successful listing - single page", + groupName: "test-group", + mockResponse: []*resourcegroups.ListGroupResourcesItem{ + { + Identifier: &resourcegroups.ResourceIdentifier{ + ResourceArn: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"), + ResourceType: aws.String("AWS::EC2::Instance"), + }, + }, + { + Identifier: &resourcegroups.ResourceIdentifier{ + ResourceArn: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-0987654321fedcba0"), + ResourceType: aws.String("AWS::EC2::Instance"), + }, + }, + }, + expectedError: false, + }, + { + name: "successful listing - multiple pages", + groupName: "test-group", + mockResponse: []*resourcegroups.ListGroupResourcesItem{ + { + Identifier: &resourcegroups.ResourceIdentifier{ + ResourceArn: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-1"), + ResourceType: aws.String("AWS::EC2::Instance"), + }, + }, + { + Identifier: &resourcegroups.ResourceIdentifier{ + ResourceArn: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-2"), + ResourceType: aws.String("AWS::EC2::Instance"), + }, + }, + { + Identifier: &resourcegroups.ResourceIdentifier{ + ResourceArn: aws.String("arn:aws:s3:us-east-1:123456789012:bucket/test-bucket"), + ResourceType: aws.String("AWS::S3::Bucket"), + }, + }, + }, + expectedError: false, + }, + { + name: "group not found", + groupName: "non-existent-group", + mockErr: fmt.Errorf("ResourceNotFoundException: Group not found"), + expectedError: true, + }, + { + name: "aws error", + groupName: "test-group", + mockErr: fmt.Errorf("AWS error"), + expectedError: true, + }, + { + name: "nil service", + groupName: "test-group", + nilService: true, + expectedError: true, + }, + { + name: "empty group name", + groupName: "", + mockErr: fmt.Errorf("ValidationException: Group name is required"), + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mock := &MockResourceGroupsClient{ + mockListGroupResourcesPages: func(input *resourcegroups.ListGroupResourcesInput, fn func(*resourcegroups.ListGroupResourcesOutput, bool) bool) error { + // Verify input + if input.GroupName == nil { + t.Error("GroupName should not be nil") + } else if *input.GroupName != tc.groupName { + t.Errorf("expected group name %s but got %s", tc.groupName, *input.GroupName) + } + + if tc.mockErr != nil { + return tc.mockErr + } + + if len(tc.mockResponse) > 2 { + // Simulate pagination for responses with more than 2 items + fn(&resourcegroups.ListGroupResourcesOutput{ + Resources: tc.mockResponse[:2], + }, false) + fn(&resourcegroups.ListGroupResourcesOutput{ + Resources: tc.mockResponse[2:], + }, true) + } else { + fn(&resourcegroups.ListGroupResourcesOutput{ + Resources: tc.mockResponse, + }, true) + } + return nil + }, + } + + rg := &ResourceGroups{ + Service: mock, + } + + if tc.nilService { + rg.Service = nil + } + + resources, err := rg.ListGroupResources(tc.groupName) + + if tc.expectedError { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resources == nil { + t.Error("expected resources but got nil") + } + if len(resources) != len(tc.mockResponse) { + t.Errorf("expected %d resources but got %d", len(tc.mockResponse), len(resources)) + } + // Verify each resource matches the mock response + for i, resource := range resources { + if *resource.Identifier.ResourceArn != *tc.mockResponse[i].Identifier.ResourceArn { + t.Errorf("expected resource ARN %s but got %s", + *tc.mockResponse[i].Identifier.ResourceArn, + *resource.Identifier.ResourceArn) + } + if *resource.Identifier.ResourceType != *tc.mockResponse[i].Identifier.ResourceType { + t.Errorf("expected resource type %s but got %s", + *tc.mockResponse[i].Identifier.ResourceType, + *resource.Identifier.ResourceType) + } + } + } + }) + } +} + +func TestDeleteGroup(t *testing.T) { + cases := []struct { + name string + groupName string + mockResponse *resourcegroups.DeleteGroupOutput + mockErr error + expectedError bool + nilService bool + }{ + { + name: "successful deletion", + groupName: "test-group", + mockResponse: &resourcegroups.DeleteGroupOutput{}, + expectedError: false, + }, + { + name: "group not found", + groupName: "non-existent-group", + mockErr: fmt.Errorf("ResourceNotFoundException: Group not found"), + expectedError: true, + }, + { + name: "aws error", + groupName: "test-group", + mockErr: fmt.Errorf("AWS error"), + expectedError: true, + }, + { + name: "nil service", + groupName: "test-group", + nilService: true, + expectedError: true, + }, + { + name: "empty group name", + groupName: "", + mockErr: fmt.Errorf("ValidationException: Group name is required"), + expectedError: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + mock := &MockResourceGroupsClient{ + mockDeleteGroup: func(input *resourcegroups.DeleteGroupInput) (*resourcegroups.DeleteGroupOutput, error) { + // Verify input + if input.GroupName == nil { + t.Error("GroupName should not be nil") + } else if *input.GroupName != tc.groupName { + t.Errorf("expected group name %s but got %s", tc.groupName, *input.GroupName) + } + + if tc.mockErr != nil { + return nil, tc.mockErr + } + return tc.mockResponse, nil + }, + } + + rg := &ResourceGroups{ + Service: mock, + } + + if tc.nilService { + rg.Service = nil + } + + err := rg.DeleteGroup(tc.groupName) + + if tc.expectedError { + if err == nil { + t.Error("expected error but got nil") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } + }) + } +}