Skip to content

Commit

Permalink
feat: only run against enabled apis, add explain-project command
Browse files Browse the repository at this point in the history
  • Loading branch information
ekristen committed May 24, 2024
1 parent 51fa697 commit c3d3353
Show file tree
Hide file tree
Showing 31 changed files with 315 additions and 126 deletions.
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ekristen/gcp-nuke/pkg/common"

_ "github.com/ekristen/gcp-nuke/pkg/commands/list"
_ "github.com/ekristen/gcp-nuke/pkg/commands/project"
_ "github.com/ekristen/gcp-nuke/pkg/commands/run"

_ "github.com/ekristen/gcp-nuke/resources"
Expand Down Expand Up @@ -39,7 +40,7 @@ func main() {

app.Commands = common.GetCommands()
app.CommandNotFound = func(context *cli.Context, command string) {
logrus.Fatalf("Command %s not found.", command)
logrus.Fatalf("command %s not found.", command)
}

if err := app.Run(os.Args); err != nil {
Expand Down
145 changes: 145 additions & 0 deletions pkg/commands/project/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package project

import (
"encoding/json"
"fmt"
"strings"

"github.com/urfave/cli/v2"

"github.com/ekristen/gcp-nuke/pkg/commands/global"
"github.com/ekristen/gcp-nuke/pkg/common"
"github.com/ekristen/gcp-nuke/pkg/gcputil"
)

type CredentialsJSON struct {
Type string `json:"type"`
ProjectID string `json:"project_id"`
PrivateKeyID string `json:"private_key_id"`
ClientEmail string `json:"client_email"`
ClientID string `json:"client_id"`
Audience string `json:"audience"`
SubjectTokenType string `json:"subject_token_type"`
TokenURL string `json:"token_url"`
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
CredentialSource struct {
File string `json:"file"`
Format struct {
Type string `json:"type"`
} `json:"format"`
} `json:"credential_source"`
}

func execute(c *cli.Context) error {
project, err := gcputil.New(c.Context, c.String("project-id"), c.String("impersonate-service-account"))
if err != nil {
return err
}

fmt.Println("Details")
fmt.Println("--------------------------------------------------")
fmt.Println(" Project ID:", project.ID())
fmt.Printf(" Enabled APIs: %d\n", len(project.GetEnabledAPIs()))
fmt.Printf(" Regions: %d\n", len(project.Regions))

creds, err := project.GetCredentials(c.Context)
if err != nil {
return err
}

var parsed CredentialsJSON
if err := json.Unmarshal(creds.JSON, &parsed); err != nil {
return err
}

fmt.Println("")
fmt.Println("Authentication:")
fmt.Println("--------------------------------------------------")
fmt.Println("> Type:", parsed.Type)

if parsed.Type == "service_account" {
fmt.Println("> Client Email:", parsed.ClientEmail)
fmt.Println("> Client ID:", parsed.ClientID)
fmt.Println("> Private Key ID:", parsed.PrivateKeyID)
} else if parsed.Type == "external_account" {
fmt.Println("> Audience:", parsed.Audience)
fmt.Println("> Service Account:",
strings.Replace(
parsed.ServiceAccountImpersonationURL,
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/", "", -1))
fmt.Println("> Source.File:", parsed.CredentialSource.File)
fmt.Println("> Source.Format:", parsed.CredentialSource.Format.Type)
if c.String("impersonate-service-account") != "" {
fmt.Println("> Impersonating:", c.String("impersonate-service-account"))
}
}

if c.Bool("with-regions") {
fmt.Println("")
fmt.Println("Regions:")
fmt.Println("--------------------------------------------------")
for _, region := range project.Regions {
fmt.Println("-", region)
}
} else {
fmt.Println("")
fmt.Println("Regions: use --with-regions to include regions in the output")
}

if c.Bool("with-apis") {
fmt.Println("")
fmt.Println("Enabled APIs:")
fmt.Println("--------------------------------------------------")
fmt.Println("**Note:** any resource that matches an API that is not enabled will be automatically skipped")
fmt.Println("")
for _, api := range project.GetEnabledAPIs() {
fmt.Println("-", api)
}
} else {
fmt.Println("")
fmt.Println("Enabled APIs: use --with-apis to include enabled APIs in the output")
}

return nil
}

func init() {
flags := []cli.Flag{
&cli.PathFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "path to config file",
Value: "config.yaml",
},
&cli.StringFlag{
Name: "project-id",
Usage: "which GCP project should be nuked",
EnvVars: []string{"GCP_NUKE_PROJECT_ID"},
Required: true,
},
&cli.StringFlag{
Name: "impersonate-service-account",
Usage: "impersonate a service account for all API calls",
EnvVars: []string{"GCP_NUKE_IMPERSONATE_SERVICE_ACCOUNT"},
},
&cli.BoolFlag{
Name: "with-regions",
Usage: "include regions in the output",
},
&cli.BoolFlag{
Name: "with-apis",
Usage: "include enabled APIs in the output",
},
}

cmd := &cli.Command{
Name: "explain-project",
Usage: "explain the project and authentication method used to authenticate against GCP",
Description: `explain the project and authentication method used to authenticate against GCP`,
Flags: append(flags, global.Flags()...),
Before: global.Before,
Action: execute,
}

common.RegisterCommand(cmd)
}
1 change: 1 addition & 0 deletions pkg/commands/run/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func execute(c *cli.Context) error {
Project: ptr.String(projectID),
Region: ptr.String(regionName),
Zones: gcp.GetZones(regionName),
EnabledAPIs: gcp.GetEnabledAPIs(),
ClientOptions: gcp.GetClientOptions(),
})); err != nil {
return err
Expand Down
29 changes: 29 additions & 0 deletions pkg/gcputil/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"fmt"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/cloudresourcemanager/v3"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"google.golang.org/api/serviceusage/v1"
"google.golang.org/protobuf/types/known/durationpb"
"log"
"strings"
Expand Down Expand Up @@ -41,6 +43,7 @@ type GCP struct {
Organizations []*Organization
Projects []*Project
Regions []string
APIS []string

ProjectID string

Expand Down Expand Up @@ -105,6 +108,14 @@ func (g *GCP) ID() string {
return g.ProjectID
}

func (g *GCP) GetEnabledAPIs() []string {
return g.APIS
}

func (g *GCP) GetCredentials(ctx context.Context) (*google.Credentials, error) {
return google.FindDefaultCredentials(ctx)
}

func New(ctx context.Context, projectID, impersonateServiceAccount string) (*GCP, error) {
gcp := &GCP{
Organizations: make([]*Organization, 0),
Expand Down Expand Up @@ -200,5 +211,23 @@ func New(ctx context.Context, projectID, impersonateServiceAccount string) (*GCP
}
}

serviceUsage, err := serviceusage.NewService(ctx, gcp.GetClientOptions()...)
if err != nil {
return nil, err
}

suReq := serviceUsage.Services.
List(fmt.Sprintf("projects/%s", projectID)).
Filter("state:ENABLED")

if suErr := suReq.Pages(ctx, func(page *serviceusage.ListServicesResponse) error {
for _, svc := range page.Services {
gcp.APIS = append(gcp.APIS, svc.Config.Name)
}
return nil
}); suErr != nil {
return nil, suErr
}

return gcp, nil
}
34 changes: 34 additions & 0 deletions pkg/nuke/resource.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,54 @@
package nuke

import (
"fmt"
liberror "github.com/ekristen/libnuke/pkg/errors"
"github.com/sirupsen/logrus"
"google.golang.org/api/option"
"slices"

"github.com/ekristen/libnuke/pkg/registry"
)

type Geography string

const (
Workspace registry.Scope = "workspace"
Organization registry.Scope = "organization"
Project registry.Scope = "project"

Global Geography = "global"
Regional Geography = "regional"
Zonal Geography = "zonal"
)

type ListerOpts struct {
Project *string
Region *string
Zones []string
EnabledAPIs []string
ClientOptions []option.ClientOption
}

func (o *ListerOpts) BeforeList(geo Geography, service string) error {
log := logrus.WithField("geo", geo).
WithField("service", service).
WithField("hooke", "true")

if geo == Global && *o.Region != "global" {
log.Trace("before-list: skipping resource, global")
return liberror.ErrSkipRequest("resource is global")
} else if geo == Regional && *o.Region == "global" {
log.Trace("before-list: skipping resource, regional")
return liberror.ErrSkipRequest("resource is regional")
}

if !slices.Contains(o.EnabledAPIs, service) {
log.Trace("before-list: skipping resource, api not enabled")
return liberror.ErrSkipRequest(fmt.Sprintf("api '%s' not enabled", service))
}

log.Trace("before-list: called")

return nil
}
8 changes: 4 additions & 4 deletions resources/cloud-function.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ func (l *CloudFunctionLister) Close() {
}

func (l *CloudFunctionLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
var resources []resource.Resource

opts := o.(*nuke.ListerOpts)
if *opts.Region == "global" {
return nil, liberror.ErrSkipRequest("resource is regional")
if err := opts.BeforeList(nuke.Regional, "cloudfunctions.googleapis.com"); err != nil {
return resources, err
}

var resources []resource.Resource

if l.svc == nil {
var err error
l.svc, err = functions.NewCloudFunctionsRESTClient(ctx, opts.ClientOptions...)
Expand Down
9 changes: 4 additions & 5 deletions resources/cloud-function2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"cloud.google.com/go/functions/apiv2"
"cloud.google.com/go/functions/apiv2/functionspb"

liberror "github.com/ekristen/libnuke/pkg/errors"
"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
"github.com/ekristen/libnuke/pkg/types"
Expand Down Expand Up @@ -43,13 +42,13 @@ func (l *CloudFunction2Lister) Close() {
}

func (l *CloudFunction2Lister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
var resources []resource.Resource

opts := o.(*nuke.ListerOpts)
if *opts.Region == "global" {
return nil, liberror.ErrSkipRequest("resource is regional")
if err := opts.BeforeList(nuke.Regional, "cloudfunctions.googleapis.com"); err != nil {
return resources, err
}

var resources []resource.Resource

if l.svc == nil {
var err error
l.svc, err = functions.NewFunctionRESTClient(ctx, opts.ClientOptions...)
Expand Down
9 changes: 4 additions & 5 deletions resources/cloud-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"cloud.google.com/go/run/apiv2"
"cloud.google.com/go/run/apiv2/runpb"

liberror "github.com/ekristen/libnuke/pkg/errors"
"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
"github.com/ekristen/libnuke/pkg/types"
Expand All @@ -37,13 +36,13 @@ type CloudRunLister struct {
}

func (l *CloudRunLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
var resources []resource.Resource

opts := o.(*nuke.ListerOpts)
if *opts.Region == "global" {
return nil, liberror.ErrSkipRequest("resource is regional")
if err := opts.BeforeList(nuke.Regional, "run.googleapis.com"); err != nil {
return resources, err
}

var resources []resource.Resource

if l.svc == nil {
var err error
l.svc, err = run.NewServicesClient(ctx, opts.ClientOptions...)
Expand Down
9 changes: 4 additions & 5 deletions resources/cloud-sql-instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/sirupsen/logrus"
sqladmin "google.golang.org/api/sqladmin/v1beta4"

liberror "github.com/ekristen/libnuke/pkg/errors"
"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
"github.com/ekristen/libnuke/pkg/types"
Expand All @@ -34,13 +33,13 @@ type CloudSQLInstanceLister struct {
}

func (l *CloudSQLInstanceLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
var resources []resource.Resource

opts := o.(*nuke.ListerOpts)
if *opts.Region == "global" {
return nil, liberror.ErrSkipRequest("resource is regional")
if err := opts.BeforeList(nuke.Regional, "sqladmin.googleapis.com"); err != nil {
return resources, err
}

var resources []resource.Resource

if l.svc == nil {
var err error
l.svc, err = sqladmin.NewService(ctx, opts.ClientOptions...)
Expand Down
Loading

0 comments on commit c3d3353

Please sign in to comment.