diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67d7ac4..557b3b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,7 @@ on: - "!**/*" tags: - "v*.*.*" + - "v*.*.*-rc*" jobs: release: @@ -18,18 +19,12 @@ jobs: check-latest: true - name: Check out code into the Go module directory - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: setup tools - run: | - mkdir ~/bin - curl -sL https://github.com/Songmu/goxz/releases/download/v0.8.1/goxz_v0.8.1_linux_amd64.tar.gz | tar zxvf - && install goxz_v0.8.1_linux_amd64/goxz ~/bin/ - curl -sL https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz | tar zxvf - && install ghr_v0.13.0_linux_amd64/ghr ~/bin/ - - - name: dist - run: PATH=~/bin:$PATH make dist - - - name: release - run: PATH=~/bin:$PATH make release + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..b2c0211 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,33 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + - go mod download +builds: + - env: + - CGO_ENABLED=0 + main: ./cmd/lambroll + binary: lambroll + ldflags: + - -s -w + - -X main.Version=v{{.Version}} + goos: + - darwin + - linux + goarch: + - amd64 + - arm64 +release: + prerelease: "true" +archives: + - name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ .Env.NIGHTLY_VERSION }}" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/Makefile b/Makefile index bdd63ad..395d331 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ DATE := $(shell date +%Y-%m-%dT%H:%M:%S%z) export GO111MODULE := on .PHONY: test binary install clean dist -cmd/lambroll/lambroll: *.go cmd/lambroll/*.go +cmd/lambroll/lambroll: *.go cmd/lambroll/*.go go.mod go.sum cd cmd/lambroll && go build -ldflags "-s -w -X main.Version=${GIT_VER}" -gcflags="-trimpath=${PWD}" install: cmd/lambroll/lambroll @@ -16,17 +16,11 @@ clean: rm -f cmd/lambroll/lambroll rm -fr dist/ -dist: - CGO_ENABLED=0 \ - goxz -pv=$(GIT_VER) \ - -build-ldflags="-s -w -X main.Version=${GIT_VER}" \ - -os=darwin,linux -arch=amd64,arm64 -d=dist ./cmd/lambroll +packages: + goreleaser build --skip-validate --rm-dist -release: - ghr -u fujiwara -r lambroll -n "$(GIT_VER)" $(GIT_VER) dist/ - -prerelease: - ghr -replace -u fujiwara -r lambroll -n "$(GIT_VER)" $(GIT_VER) dist/ +packages-snapshot: + goreleaser build --skip-validate --rm-dist --snapshot orb/publish: circleci orb validate circleci-orb.yml diff --git a/README.md b/README.md index a9213b8..e50c2a2 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ https://circleci.com/orbs/registry/orb/fujiwara/lambroll ```yml version: 2.1 orbs: - lambroll: fujiwara/lambroll@0.0.8 + lambroll: fujiwara/lambroll@2.0.1 jobs: deloy: docker: @@ -56,7 +56,7 @@ jobs: steps: - checkout - lambroll/install: - version: v0.12.2 + version: v1.0.0 - run: command: | lambroll deploy @@ -71,10 +71,10 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: fujiwara/lambroll@v0 + - uses: actions/checkout@v4 + - uses: fujiwara/lambroll@v1 with: - version: v0.12.2 + version: v1.0.0 - run: | lambroll deploy ``` @@ -127,20 +127,19 @@ $ lambroll deploy Usage: lambroll Flags: - -h, --help Show context-sensitive help. - --function=STRING Function file path - --log-level="info" log level (trace, debug, info, warn, error) - --color enable colored output - --region=REGION AWS region - --profile=PROFILE AWS credential profile name - --tfstate=TFSTATE URL to terraform.tfstate - --prefixed-tfstate=KEY=VALUE;... - key value pair of the prefix for template function name - and URL to terraform.tfstate - --endpoint=ENDPOINT AWS API Lambda Endpoint - --envfile=ENVFILE,... environment files - --ext-str=KEY=VALUE;... external string values for Jsonnet - --ext-code=KEY=VALUE;... external code values for Jsonnet + -h, --help Show context-sensitive help. + --function=STRING Function file path ($LAMBROLL_FUNCTION) + --log-level="info" log level (trace, debug, info, warn, error) ($LAMBROLL_LOGLEVEL) + --color enable colored output ($LAMBROLL_COLOR) + --region=REGION AWS region ($AWS_REGION) + --profile=PROFILE AWS credential profile name ($AWS_PROFILE) + --tfstate=TFSTATE URL to terraform.tfstate ($LAMBROLL_TFSTATE) + --prefixed-tfstate=KEY=VALUE;... key value pair of the prefix for template function name and URL to + terraform.tfstate ($LAMBROLL_PREFIXED_TFSTATE) + --endpoint=ENDPOINT AWS API Lambda Endpoint ($AWS_LAMBDA_ENDPOINT) + --envfile=ENVFILE,... environment files ($LAMBROLL_ENVFILE) + --ext-str=KEY=VALUE;... external string values for Jsonnet ($LAMBROLL_EXTSTR) + --ext-code=KEY=VALUE;... external code values for Jsonnet ($LAMBROLL_EXTCODE) Commands: deploy @@ -170,11 +169,19 @@ Commands: render render function.json + status + show status of function + + delete + delete function + versions show versions of function version show version + +Run "lambroll --help" for more information on a command. ``` ### Init @@ -182,14 +189,16 @@ Commands: `lambroll init` initialize function.json by existing function. ```console -usage: lambroll init --function-name=FUNCTION-NAME [] +Usage: lambroll init --function-name= init function.json Flags: - (common flags snipped) - --function-name=FUNCTION-NAME Function name for initialize - --download Download function.zip + --function-name= Function name for init + --download-zip Download function.zip + --jsonnet render function.json as jsonnet + --qualifier=QUALIFIER function version or alias + --function-url create function url definition file ``` `init` creates `function.json` as a configuration file of the function. @@ -197,21 +206,23 @@ Flags: ### Deploy ```console -usage: lambroll deploy [] +Usage: lambroll deploy deploy or create function Flags: - (common flags snipped) - --src="." function zip archive or src dir - --exclude-file=".lambdaignore" - exclude file - --dry-run dry run - --publish publish function - --alias="current" alias name for publish - --alias-to-latest set alias to unpublished $LATEST version - --skip-archive skip to create zip archive. requires Code.S3Bucket and Code.S3Key in function definition - --keep-versions=0 Number of latest versions to keep. Older versions will be deleted. (Optional value: default 0). + --src="." function zip archive or src dir + --publish publish function + --alias-name="current" alias name for publish + --alias-to-latest set alias to unpublished $LATEST version + --dry-run dry run + --skip-archive skip to create zip archive. requires Code.S3Bucket and Code.S3Key in function + definition + --keep-versions=0 Number of latest versions to keep. Older versions will be deleted. (Optional + value: default 0). + --function-url="" path to function-url definiton + --skip-function skip to deploy a function. deploy function-url only + --exclude-file=".lambdaignore" exclude file ``` `deploy` works as below. @@ -242,14 +253,13 @@ PackageType=Image and Code.ImageUri are required in function.json. ### Rollback ``` -usage: lambroll rollback [] +Usage: lambroll rollback rollback function Flags: - (common flags snipped) - --delete-version Delete rolled back version - --dry-run dry run + --dry-run dry run + --delete-version delete rolled back version ``` `lambroll deploy` create/update alias `current` to the published function version on deploy. @@ -263,15 +273,14 @@ Flags: ### Invoke ``` -usage: lambroll invoke [] +Usage: lambroll invoke invoke function Flags: - (common flags snipped) - --async invocation type async - --log-tail output tail of log to STDERR - --qualifier=QUALIFIER version or alias to invoke + --async invocation type async + --log-tail output tail of log to STDERR + --qualifier=QUALIFIER version or alias to invoke ``` `lambroll invoke` accepts multiple JSON payloads for invocations from STDIN. @@ -474,7 +483,7 @@ lambroll also can read function.jsonnet as [Jsonnet](https://jsonnet.org/) forma Handler: 'index.handler', MemorySize: std.extVar('memorySize'), Role: 'arn:aws:iam::%s:role/lambda_role' % [ std.extVar('accountID') ], - Runtime: 'nodejs14.x', + Runtime: 'nodejs20.x', } ``` @@ -512,6 +521,94 @@ Edge functions require two preconditions: Otherwise, it works as usual. +### Lambda Function URLs support + +lambroll can deploy [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). + +`lambroll deploy --function-url=function_url.json` deploys a function URL after the function deploied. + +When you want to deploy a public (without authentication) function URL, `function_url.json` is shown below. + +```json +{ + "Config": { + "AuthType": "NONE" + } +} +``` + +When you want to deploy a private (requires AWS IAM authentication) function URL, `function_url.json` is shown below. + +```json +{ + "Config": { + "AuthType": "AWS_IAM" + }, + "Permissions": [ + { + "Principal": "0123456789012" + }, + { + "PrincipalOrgID": "o-123456789", + "Principal": "*" + } + ] +} +``` + +- `Config` maps to [CreateFunctionUrlConfigInput](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/lambda#CreateFunctionUrlConfigInput) in AWS SDK Go v2. + - `Config.AuthType` must be `AWS_IAM` or `NONE`. + - `Config.Qualifer` is optional. +- `Permissions` is optional. + - If `Permissions` is not defined and `AuthType` is `NONE`, `Principal` is set to `*` automatically. + - When `AuthType` is `AWS_IAM`, you must define `Permissions` to specify allowed principals. + - Each elements of `Permissons` maps to [AddPermissionInput](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/lambda#AddPermissionInput) in AWS SDK Go v2. +- `function_url.jsonnet` is also supported like `function.jsonnet`. + +### FunctionURL support + +lambroll can deploy [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). + +`lambroll deploy --function-url=function_url.json` deploys a function URL after the function deploied. + +When you want to deploy a public (without authentication) function URL, `function_url.json` is shown below. + +```json +{ + "Config": { + "AuthType": "NONE" + } +} +``` + +When you want to deploy a private (requires AWS IAM authentication) function URL, `function_url.json` is shown below. + +```json +{ + "Config": { + "AuthType": "AWS_IAM" + }, + "Permissions": [ + { + "Principal": "0123456789012" + }, + { + "PrincipalOrgID": "o-123456789", + "Principal": "*" + } + ] +} +``` + +- `Config` maps to [CreateFunctionUrlConfigInput](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/lambda#CreateFunctionUrlConfigInput) in AWS SDK Go v2. + - `Config.AuthType` must be `AWS_IAM` or `NONE`. + - `Config.Qualifer` is optional. +- `Permissions` is optional. + - If `Permissions` is not defined and `AuthType` is `NONE`, `Principal` is set to `*` automatically. + - When `AuthType` is `AWS_IAM`, you must define `Permissions` to specify allowed principals. + - Each elements of `Permissons` maps to [AddPermissionInput](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/lambda#AddPermissionInput) in AWS SDK Go v2. +- `function_url.jsonnet` is also supported like `function.jsonnet`. + ## LICENSE MIT License diff --git a/action.yml b/action.yml index da09d60..5660254 100644 --- a/action.yml +++ b/action.yml @@ -1,13 +1,13 @@ inputs: version: description: "A version to install lamroll" - default: "v0.10.0" + default: "v1.0.0" runs: using: "composite" steps: - run: | - cd /tmp + mkdir -p /tmp/lambroll-${{ inputs.version }} + cd /tmp/lambroll-${{ inputs.version }} curl -sL https://github.com/fujiwara/lambroll/releases/download/${{ inputs.version }}/lambroll_${{ inputs.version}}_linux_amd64.tar.gz | tar zxvf - - sudo install lambroll_${{ inputs.version }}_linux_amd64/lambroll /usr/local/bin - rm -rf lambroll_${{ inputs.version }}_linux_amd64/ + sudo install lambroll /usr/local/bin shell: bash diff --git a/circleci-orb.yml b/circleci-orb.yml index 79b2251..883c035 100644 --- a/circleci-orb.yml +++ b/circleci-orb.yml @@ -12,32 +12,20 @@ commands: version: description: lambroll version type: string - default: v0.10.0 + default: v1.0.0 steps: - detect/init - run: name: "Install lambroll" command: | - case $OSD_FAMILY in - linux) - PKG_EXT=tar.gz - EXTRACT="tar zxvf" - ;; - darwin) - PKG_EXT=zip - EXTRACT="unzip" - ;; - *) - echo "Unsupported operating system. $OSD_FAMILY" - exit 1 - ;; - esac + mkdir -p /tmp/lambroll-<< parameters.version >> + cd /tmp/lambroll-<< parameters.version >> DIRNAME=lambroll_<< parameters.version >>_${OSD_FAMILY}_amd64 - FILENAME=${DIRNAME}.${PKG_EXT} + FILENAME=${DIRNAME}.tar.gz LAMBROLL_URL=https://github.com/fujiwara/lambroll/releases/download/<< parameters.version >>/${FILENAME} - curl -L $LAMBROLL_URL > /tmp/${FILENAME} - $EXTRACT /tmp/${FILENAME} - sudo install ${DIRNAME}/lambroll /usr/local/bin + curl -sL $LAMBROLL_URL > ${FILENAME} + tar zxvf ${FILENAME} + sudo install lambroll /usr/local/bin examples: install_lambroll: diff --git a/cli.go b/cli.go index 39161d7..5c3a3b3 100644 --- a/cli.go +++ b/cli.go @@ -13,18 +13,18 @@ import ( ) type Option struct { - Function string `help:"Function file path"` - LogLevel string `help:"log level (trace, debug, info, warn, error)" default:"info" enum:"trace,debug,info,warn,error"` - Color bool `help:"enable colored output" default:"false"` + Function string `help:"Function file path" env:"LAMBROLL_FUNCTION"` + LogLevel string `help:"log level (trace, debug, info, warn, error)" default:"info" enum:"trace,debug,info,warn,error" env:"LAMBROLL_LOGLEVEL"` + Color bool `help:"enable colored output" default:"false" env:"LAMBROLL_COLOR"` - Region *string `help:"AWS region" environment:"AWS_REGION"` - Profile *string `help:"AWS credential profile name" environment:"AWS_PROFILE"` - TFState *string `name:"tfstate" help:"URL to terraform.tfstate"` - PrefixedTFState map[string]string `name:"prefixed-tfstate" help:"key value pair of the prefix for template function name and URL to terraform.tfstate"` - Endpoint *string `help:"AWS API Lambda Endpoint"` - Envfile []string `help:"environment files"` - ExtStr map[string]string `help:"external string values for Jsonnet"` - ExtCode map[string]string `help:"external code values for Jsonnet"` + Region *string `help:"AWS region" env:"AWS_REGION"` + Profile *string `help:"AWS credential profile name" env:"AWS_PROFILE"` + TFState *string `name:"tfstate" help:"URL to terraform.tfstate" env:"LAMBROLL_TFSTATE"` + PrefixedTFState map[string]string `name:"prefixed-tfstate" help:"key value pair of the prefix for template function name and URL to terraform.tfstate" env:"LAMBROLL_PREFIXED_TFSTATE"` + Endpoint *string `help:"AWS API Lambda Endpoint" env:"AWS_LAMBDA_ENDPOINT"` + Envfile []string `help:"environment files" env:"LAMBROLL_ENVFILE"` + ExtStr map[string]string `help:"external string values for Jsonnet" env:"LAMBROLL_EXTSTR"` + ExtCode map[string]string `help:"external code values for Jsonnet" env:"LAMBROLL_EXTCODE"` } type CLIOptions struct { @@ -39,6 +39,8 @@ type CLIOptions struct { Logs *LogsOption `cmd:"logs" help:"show logs of function"` Diff *DiffOption `cmd:"diff" help:"show diff of function"` Render *RenderOption `cmd:"render" help:"render function.json"` + Status *StatusOption `cmd:"status" help:"show status of function"` + Delete *DeleteOption `cmd:"delete" help:"delete function"` Versions *VersionsOption `cmd:"versions" help:"show versions of function"` Version struct{} `cmd:"version" help:"show version"` @@ -128,6 +130,10 @@ func dispatchCLI(ctx context.Context, sub string, usage func(), opts *CLIOptions return app.Render(ctx, opts.Render) case "diff": return app.Diff(ctx, opts.Diff) + case "delete": + return app.Delete(ctx, opts.Delete) + case "status": + return app.Status(ctx, opts.Status) default: usage() } diff --git a/delete.go b/delete.go index 7a03f10..a8e7692 100644 --- a/delete.go +++ b/delete.go @@ -5,23 +5,25 @@ import ( "fmt" "log" + "github.com/Songmu/prompter" "github.com/aws/aws-sdk-go-v2/service/lambda" ) // DeleteOption represents options for Delete() type DeleteOption struct { - DryRun *bool + DryRun bool `help:"dry run" default:"false" negatable:""` + Force bool `help:"delete without confirmation" default:"false"` } func (opt DeleteOption) label() string { - if *opt.DryRun { + if opt.DryRun { return "**DRY RUN**" } return "" } // Delete deletes function -func (app *App) Delete(ctx context.Context, opt DeleteOption) error { +func (app *App) Delete(ctx context.Context, opt *DeleteOption) error { fn, err := app.loadFunction(app.functionFilePath) if err != nil { return fmt.Errorf("failed to load function: %w", err) @@ -29,9 +31,15 @@ func (app *App) Delete(ctx context.Context, opt DeleteOption) error { log.Println("[info] deleting function", *fn.FunctionName, opt.label()) - if *opt.DryRun { + if opt.DryRun { return nil } + + if !opt.Force && !prompter.YN("Do you want to delete the function?", false) { + log.Println("[info] canceled to delete function", *fn.FunctionName) + return nil + } + _, err = app.lambda.DeleteFunction(ctx, &lambda.DeleteFunctionInput{ FunctionName: fn.FunctionName, }) @@ -39,5 +47,7 @@ func (app *App) Delete(ctx context.Context, opt DeleteOption) error { return fmt.Errorf("failed to delete function: %w", err) } + log.Println("[info] completed to delete function", *fn.FunctionName) + return nil } diff --git a/deploy.go b/deploy.go index fad9930..414a8d1 100644 --- a/deploy.go +++ b/deploy.go @@ -26,6 +26,8 @@ type DeployOption struct { SkipArchive bool `help:"skip to create zip archive. requires Code.S3Bucket and Code.S3Key in function definition" default:"false"` KeepVersions int `help:"Number of latest versions to keep. Older versions will be deleted. (Optional value: default 0)." default:"0"` Ignore string `help:"ignore fields by jq queries in function.json" default:""` + FunctionURL string `help:"path to function-url definiton" default:"" env:"LAMBROLL_FUNCTION_URL"` + SkipFunction bool `help:"skip to deploy a function. deploy function-url only" default:"false"` ExcludeFileOption } @@ -81,6 +83,22 @@ func (app *App) Deploy(ctx context.Context, opt *DeployOption) error { return fmt.Errorf("failed to load function: %w", err) } + deployFunctionURL := func(context.Context) error { return nil } + if opt.FunctionURL != "" { + deployFunctionURL = func(ctx context.Context) error { + fc, err := app.loadFunctionUrl(opt.FunctionURL, *fn.FunctionName) + if err != nil { + return fmt.Errorf("failed to load function url config: %w", err) + } + return app.deployFunctionURL(ctx, fc) + } + } + + if opt.SkipFunction { + // skip to deploy a function. deploy function-url only + return deployFunctionURL(ctx) + } + log.Printf("[info] starting deploy function %s", *fn.FunctionName) if current, err := app.lambda.GetFunction(ctx, &lambda.GetFunctionInput{ FunctionName: fn.FunctionName, @@ -126,6 +144,7 @@ func (app *App) Deploy(ctx context.Context, opt *DeployOption) error { Handler: fn.Handler, KMSKeyArn: fn.KMSKeyArn, Layers: fn.Layers, + LoggingConfig: fn.LoggingConfig, MemorySize: fn.MemorySize, Role: fn.Role, Runtime: fn.Runtime, @@ -200,6 +219,11 @@ func (app *App) Deploy(ctx context.Context, opt *DeployOption) error { if opt.KeepVersions > 0 { // Ignore zero-value. return app.deleteVersions(ctx, *fn.FunctionName, opt.KeepVersions) } + + if err := deployFunctionURL(ctx); err != nil { + return err + } + return nil } diff --git a/diff.go b/diff.go index 8ffd11e..0cdd0be 100644 --- a/diff.go +++ b/diff.go @@ -4,8 +4,10 @@ import ( "context" "crypto/sha256" "encoding/base64" + "errors" "fmt" "io" + "log" "strings" "github.com/aereal/jsondiff" @@ -20,10 +22,11 @@ import ( // DiffOption represents options for Diff() type DiffOption struct { - Src string `help:"function zip archive or src dir" default:"."` - CodeSha256 bool `help:"diff of code sha256" default:"false"` - Qualifier string `help:"compare with" default:"$LATEST"` - Ignore string `help:"ignore diff by jq query" default:""` + Src string `help:"function zip archive or src dir" default:"."` + CodeSha256 bool `help:"diff of code sha256" default:"false"` + Qualifier *string `help:"the qualifier to compare"` + FunctionURL string `help:"path to function-url definiton" default:"" env:"LAMBROLL_FUNCTION_URL"` + Ignore string `help:"ignore diff by jq query" default:""` ExcludeFileOption } @@ -49,20 +52,26 @@ func (app *App) Diff(ctx context.Context, opt *DiffOption) error { var packageType types.PackageType if res, err := app.lambda.GetFunction(ctx, &lambda.GetFunctionInput{ FunctionName: &name, - Qualifier: &opt.Qualifier, + Qualifier: opt.Qualifier, }); err != nil { - return fmt.Errorf("failed to GetFunction %s: %w", name, err) + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + log.Printf("[info] function %s is not found. lambroll deploy will create a new function.", name) + } else { + return fmt.Errorf("failed to GetFunction %s: %w", name, err) + } } else { remote = res.Configuration code = res.Code { + log.Println("[debug] list tags Resource", app.functionArn(ctx, name)) res, err := app.lambda.ListTags(ctx, &lambda.ListTagsInput{ // Tagging operations are permitted on Lambda functions only. // Tags on aliases and versions are not supported. Resource: aws.String(app.functionArn(ctx, name)), }) if err != nil { - return fmt.Errorf("faled to list tags: %w", err) + return fmt.Errorf("failed to list tags: %w", err) } tags = res.Tags } @@ -83,7 +92,7 @@ func (app *App) Diff(ctx context.Context, opt *DiffOption) error { remoteJSON, _ := marshalAny(remoteFunc) newJSON, _ := marshalAny(newFunc) - remoteArn := app.functionArn(ctx, name) + ":" + opt.Qualifier + remoteArn := fullQualifiedFunctionName(app.functionArn(ctx, name), opt.Qualifier) if diff, err := jsondiff.Diff( &jsondiff.Input{Name: remoteArn, X: remoteJSON}, @@ -99,7 +108,7 @@ func (app *App) Diff(ctx context.Context, opt *DiffOption) error { return err } - if opt.CodeSha256 { + if opt.CodeSha256 && latest != nil { if packageType != types.PackageTypeZip { return fmt.Errorf("code-sha256 is only supported for Zip package type") } @@ -120,6 +129,88 @@ func (app *App) Diff(ctx context.Context, opt *DiffOption) error { } } + if opt.FunctionURL == "" { + return nil + } + + if err := app.diffFunctionURL(ctx, name, opt); err != nil { + return err + } + return nil +} + +func (app *App) diffFunctionURL(ctx context.Context, name string, opt *DiffOption) error { + var remote, local *types.FunctionUrlConfig + fqName := fullQualifiedFunctionName(name, opt.Qualifier) + + fu, err := app.loadFunctionUrl(opt.FunctionURL, name) + if err != nil { + return fmt.Errorf("failed to load function-url: %w", err) + } else { + fillDefaultValuesFunctionUrlConfig(fu.Config) + local = &types.FunctionUrlConfig{ + AuthType: fu.Config.AuthType, + Cors: fu.Config.Cors, + InvokeMode: fu.Config.InvokeMode, + } + } + + if res, err := app.lambda.GetFunctionUrlConfig(ctx, &lambda.GetFunctionUrlConfigInput{ + FunctionName: &name, + Qualifier: opt.Qualifier, + }); err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + // empty + remote = &types.FunctionUrlConfig{} + } else { + return fmt.Errorf("failed to get function url config: %w", err) + } + } else { + log.Println("[debug] FunctionUrlConfig found") + remote = &types.FunctionUrlConfig{ + AuthType: res.AuthType, + Cors: res.Cors, + InvokeMode: res.InvokeMode, + } + } + r, _ := marshalJSON(remote) + l, _ := marshalJSON(local) + + if opt.Unified { + edits := myers.ComputeEdits(span.URIFromPath(fqName), string(r), string(l)) + if ds := fmt.Sprint(gotextdiff.ToUnified(fqName, opt.FunctionURL, string(r), edits)); ds != "" { + fmt.Print(coloredDiff(ds)) + } + } else { + if ds := diff.Diff(string(r), string(l)); ds != "" { + fmt.Println(color.RedString("---" + fqName)) + fmt.Println(color.GreenString("+++" + opt.FunctionURL)) + fmt.Print(coloredDiff(ds)) + } + } + + // permissions + adds, removes, err := app.calcFunctionURLPermissionsDiff(ctx, fu) + if err != nil { + return err + } + var addsB []byte + for _, in := range adds { + b, _ := marshalJSON(in) + addsB = append(addsB, b...) + } + var removesB []byte + for _, in := range removes { + b, _ := marshalJSON(in) + removesB = append(removesB, b...) + } + if ds := diff.Diff(string(removesB), string(addsB)); ds != "" { + fmt.Println(color.RedString("---")) + fmt.Println(color.GreenString("+++")) + fmt.Print(coloredDiff(ds)) + } + return nil } diff --git a/function_test.go b/function_test.go index 6f7c30c..9ea6ca1 100644 --- a/function_test.go +++ b/function_test.go @@ -60,6 +60,9 @@ func TestLoadFunction(t *testing.T) { if len(arch) != 1 || arch[0] != "x86_64" { t.Errorf("unexpected Architectures %v", fn.Architectures) } + if *fn.LoggingConfig.LogGroup != "/aws/lambda/test/json" { + t.Errorf("unexpected LoggingConfig %v", fn.LoggingConfig) + } if *fn.EphemeralStorage.Size != 1024 { t.Errorf("unexpected EphemeralStorage %v", fn.EphemeralStorage) } diff --git a/functionurl.go b/functionurl.go new file mode 100644 index 0000000..da9717d --- /dev/null +++ b/functionurl.go @@ -0,0 +1,425 @@ +package lambroll + +import ( + "context" + "crypto/sha1" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "regexp" + "sort" + "sync" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/lambda" + "github.com/aws/aws-sdk-go-v2/service/lambda/types" + "github.com/samber/lo" +) + +var ( + SidPattern = regexp.MustCompile("^lambroll-[0-9a-f]+$") + SidFormat = "lambroll-%x" +) + +type FunctionURL struct { + Config *FunctionURLConfig `json:"Config"` + Permissions FunctionURLPermissions `json:"Permissions"` +} + +func (f *FunctionURL) Validate(functionName string) error { + if f.Config == nil { + return errors.New("function url 'Config' attribute is required") + } + f.Config.FunctionName = aws.String(functionName) + // fill default values + switch f.Config.AuthType { + case types.FunctionUrlAuthTypeNone: + if len(f.Permissions) == 0 { + f.Permissions = append(f.Permissions, &FunctionURLPermission{ + AddPermissionInput: lambda.AddPermissionInput{ + Principal: aws.String("*"), + }, + }) + } + case types.FunctionUrlAuthTypeAwsIam: + if len(f.Permissions) == 0 { + return fmt.Errorf("function url 'Permissions' attribute is required when 'AuthType' is '%s'", types.FunctionUrlAuthTypeAwsIam) + } + default: + return fmt.Errorf("unknown function url 'AuthType': %s", f.Config.AuthType) + } + return nil +} + +type FunctionURLConfig = lambda.CreateFunctionUrlConfigInput + +type FunctionURLPermissions []*FunctionURLPermission + +func (ps FunctionURLPermissions) Sids() []string { + sids := make([]string, 0, len(ps)) + for _, p := range ps { + sids = append(sids, p.Sid()) + } + sort.Strings(sids) + return sids +} + +func (ps FunctionURLPermissions) Find(sid string) *FunctionURLPermission { + for _, p := range ps { + if p.Sid() == sid { + return p + } + } + return nil +} + +type FunctionURLPermission struct { + lambda.AddPermissionInput + + sid string + once sync.Once +} + +func (p *FunctionURLPermission) Sid() string { + p.once.Do(func() { + b, _ := json.Marshal(p) + h := sha1.Sum(b) + p.sid = fmt.Sprintf(SidFormat, h) + }) + return p.sid +} + +type PolicyOutput struct { + Id string `json:"Id"` + Version string `json:"Version"` + Statement []PolicyStatement `json:"Statement"` +} + +type PolicyStatement struct { + Sid string `json:"Sid"` + Effect string `json:"Effect"` + Principal any `json:"Principal"` + Action string `json:"Action"` + Resource any `json:"Resource"` + Condition any `json:"Condition"` +} + +func (ps *PolicyStatement) PrincipalAccountID() *string { + if ps.Principal == nil { + return nil + } + switch v := ps.Principal.(type) { + case string: + return aws.String(v) + case map[string]interface{}: + if v["AWS"] == nil { + return nil + } + switch vv := v["AWS"].(type) { + case string: + if a, err := arn.Parse(vv); err == nil { + return aws.String(a.AccountID) + } + return aws.String(vv) + } + } + return nil +} + +func (ps *PolicyStatement) PrincipalOrgID() *string { + principal := ps.PrincipalAccountID() + if principal == nil || *principal != "*" { + return nil + } + m, ok := ps.Condition.(map[string]interface{}) + if !ok { + return nil + } + if m["StringEquals"] == nil { + return nil + } + mm, ok := m["StringEquals"].(map[string]interface{}) + if !ok { + return nil + } + if mm["lambda:FunctionUrlAuthType"] == nil { + return nil + } + if v, ok := mm["lambda:FunctionUrlAuthType"].(string); ok && v != "AWS_IAM" { + return nil + } + if mm["aws:PrincipalOrgID"] == nil { + return nil + } + if v, ok := mm["aws:PrincipalOrgID"].(string); ok { + return aws.String(v) + } + return nil +} + +func (app *App) loadFunctionUrl(path string, functionName string) (*FunctionURL, error) { + f, err := loadDefinitionFile[FunctionURL](app, path, DefaultFunctionURLFilenames) + if err != nil { + return nil, err + } + if err := f.Validate(functionName); err != nil { + return nil, err + } + return f, nil +} + +func (app *App) deployFunctionURL(ctx context.Context, fc *FunctionURL) error { + log.Println("[info] deploying function url...") + + if err := app.deployFunctionURLConfig(ctx, fc); err != nil { + return fmt.Errorf("failed to deploy function url config: %w", err) + } + + if err := app.deployFunctionURLPermissions(ctx, fc); err != nil { + return fmt.Errorf("failed to deploy function url permissions: %w", err) + } + + log.Println("[info] deployed function url") + return nil +} + +func (app *App) deployFunctionURLConfig(ctx context.Context, fc *FunctionURL) error { + create := false + fqFunctionName := fullQualifiedFunctionName(*fc.Config.FunctionName, fc.Config.Qualifier) + functinoUrlConfig, err := app.lambda.GetFunctionUrlConfig(ctx, &lambda.GetFunctionUrlConfigInput{ + FunctionName: fc.Config.FunctionName, + Qualifier: fc.Config.Qualifier, + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + log.Printf("[info] function url config for %s not found. creating", fqFunctionName) + create = true + } else { + return fmt.Errorf("failed to get function url config: %w", err) + } + } + + if create { + res, err := app.lambda.CreateFunctionUrlConfig(ctx, fc.Config) + if err != nil { + return fmt.Errorf("failed to create function url config: %w", err) + } + log.Printf("[info] created function url config for %s", fqFunctionName) + log.Printf("[info] Function URL: %s", *res.FunctionUrl) + } else { + log.Printf("[info] updating function url config for %s", fqFunctionName) + if functinoUrlConfig.Cors != nil && fc.Config.Cors == nil { + // reset cors config + fc.Config.Cors = &types.Cors{} + } + res, err := app.lambda.UpdateFunctionUrlConfig(ctx, &lambda.UpdateFunctionUrlConfigInput{ + FunctionName: fc.Config.FunctionName, + Qualifier: fc.Config.Qualifier, + AuthType: fc.Config.AuthType, + Cors: fc.Config.Cors, + InvokeMode: fc.Config.InvokeMode, + }) + if err != nil { + return fmt.Errorf("failed to update function url config: %w", err) + } + log.Printf("[info] updated function url config for %s", fqFunctionName) + log.Printf("[info] Function URL: %s", *res.FunctionUrl) + } + return nil +} + +func (app *App) deployFunctionURLPermissions(ctx context.Context, fc *FunctionURL) error { + adds, removes, err := app.calcFunctionURLPermissionsDiff(ctx, fc) + if err != nil { + return err + } + if len(adds) == 0 && len(removes) == 0 { + log.Println("[info] no changes in permissions.") + return nil + } + + log.Printf("[info] adding %d permissions", len(adds)) + for _, in := range adds { + if _, err := app.lambda.AddPermission(ctx, in); err != nil { + return fmt.Errorf("failed to add permission: %w", err) + } + log.Printf("[info] added permission Sid: %s", *in.StatementId) + } + + log.Printf("[info] removing %d permissions", len(removes)) + for _, in := range removes { + if _, err := app.lambda.RemovePermission(ctx, in); err != nil { + return fmt.Errorf("failed to remove permission: %w", err) + } + log.Printf("[info] removed permission Sid: %s", *in.StatementId) + } + + return nil +} + +func (app *App) calcFunctionURLPermissionsDiff(ctx context.Context, fc *FunctionURL) ([]*lambda.AddPermissionInput, []*lambda.RemovePermissionInput, error) { + fqFunctionName := fullQualifiedFunctionName(*fc.Config.FunctionName, fc.Config.Qualifier) + existsSids := []string{} + { + res, err := app.lambda.GetPolicy(ctx, &lambda.GetPolicyInput{ + FunctionName: fc.Config.FunctionName, + Qualifier: fc.Config.Qualifier, + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + // do nothing + } else { + return nil, nil, fmt.Errorf("failed to get policy: %w", err) + } + } + if res != nil { + log.Printf("[debug] policy for %s: %s", fqFunctionName, *res.Policy) + var policy PolicyOutput + if err := json.Unmarshal([]byte(*res.Policy), &policy); err != nil { + return nil, nil, fmt.Errorf("failed to unmarshal policy: %w", err) + } + for _, s := range policy.Statement { + if s.Action != "lambda:InvokeFunctionUrl" || s.Effect != "Allow" { + // not a lambda function url policy + continue + } + existsSids = append(existsSids, s.Sid) + } + sort.Strings(existsSids) + } + } + + removeSids, addSids := lo.Difference(existsSids, fc.Permissions.Sids()) + if len(removeSids) == 0 && len(addSids) == 0 { + return nil, nil, nil + } + + var adds []*lambda.AddPermissionInput + for _, sid := range addSids { + p := fc.Permissions.Find(sid) + if p == nil { + // should not happen + panic(fmt.Sprintf("permission not found: %s", sid)) + } + in := &lambda.AddPermissionInput{ + Action: aws.String("lambda:InvokeFunctionUrl"), + FunctionName: fc.Config.FunctionName, + Qualifier: fc.Config.Qualifier, + FunctionUrlAuthType: fc.Config.AuthType, + StatementId: aws.String(sid), + Principal: p.Principal, + PrincipalOrgID: p.PrincipalOrgID, + } + adds = append(adds, in) + } + + var removes []*lambda.RemovePermissionInput + for _, sid := range removeSids { + in := &lambda.RemovePermissionInput{ + FunctionName: fc.Config.FunctionName, + Qualifier: fc.Config.Qualifier, + StatementId: aws.String(sid), + } + removes = append(removes, in) + } + + return adds, removes, nil +} + +func (app *App) initFunctionURL(ctx context.Context, fn *Function, opt *InitOption) error { + fc, err := app.lambda.GetFunctionUrlConfig(ctx, &lambda.GetFunctionUrlConfigInput{ + FunctionName: fn.FunctionName, + Qualifier: opt.Qualifier, + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + log.Printf("[warn] function url config for %s not found", *fn.FunctionName) + return nil + } else { + return fmt.Errorf("failed to get function url config: %w", err) + } + } + fqFunctionName := fullQualifiedFunctionName(*fn.FunctionName, opt.Qualifier) + fu := &FunctionURL{ + Config: &lambda.CreateFunctionUrlConfigInput{ + Cors: fc.Cors, + AuthType: fc.AuthType, + InvokeMode: fc.InvokeMode, + Qualifier: opt.Qualifier, + }, + } + + { + res, err := app.lambda.GetPolicy(ctx, &lambda.GetPolicyInput{ + FunctionName: fn.FunctionName, + Qualifier: opt.Qualifier, + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + // do nothing + } else { + return fmt.Errorf("failed to get policy: %w", err) + } + } + if res != nil { + log.Printf("[debug] policy for %s: %s", fqFunctionName, *res.Policy) + var policy PolicyOutput + if err := json.Unmarshal([]byte(*res.Policy), &policy); err != nil { + return fmt.Errorf("failed to unmarshal policy: %w", err) + } + for _, s := range policy.Statement { + if s.Action != "lambda:InvokeFunctionUrl" || s.Effect != "Allow" { + // not a lambda function url policy + continue + } + b, _ := marshalJSON(s) + log.Printf("[debug] statement: %s", string(b)) + pm := &FunctionURLPermission{ + AddPermissionInput: lambda.AddPermissionInput{ + Principal: s.PrincipalAccountID(), + PrincipalOrgID: s.PrincipalOrgID(), + }, + } + b, _ = marshalJSON(pm) + log.Printf("[debug] permission: %s", string(b)) + fu.Permissions = append(fu.Permissions, pm) + } + } + } + + var name string + if opt.Jsonnet { + name = DefaultFunctionURLFilenames[1] + } else { + name = DefaultFunctionURLFilenames[0] + } + log.Printf("[info] creating %s", name) + b, _ := marshalJSON(fu) + if opt.Jsonnet { + b, err = jsonToJsonnet(b, name) + if err != nil { + return err + } + } + if err := app.saveFile(name, b, os.FileMode(0644)); err != nil { + return err + } + + return nil +} + +func fillDefaultValuesFunctionUrlConfig(fc *FunctionURLConfig) { + if fc.AuthType == "" { + fc.AuthType = types.FunctionUrlAuthTypeNone + } + if fc.InvokeMode == "" { + fc.InvokeMode = types.InvokeModeBuffered + } +} diff --git a/functionurl_test.go b/functionurl_test.go new file mode 100644 index 0000000..f44f514 --- /dev/null +++ b/functionurl_test.go @@ -0,0 +1,87 @@ +package lambroll_test + +import ( + "encoding/json" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/fujiwara/lambroll" + "github.com/go-test/deep" +) + +var permissonsTestCases = []struct { + subject string + statementJSON []byte + expectedPrincipal *string + expectedPrincipalOrgID *string +}{ + { + subject: "AuthType NONE", + statementJSON: []byte(`{ + "Action": "lambda:InvokeFunctionUrl", + "Condition": { + "StringEquals": { + "lambda:FunctionUrlAuthType": "NONE" + } + }, + "Effect": "Allow", + "Principal": "*", + "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:hello", + "Sid": "lambroll-8f4ec83e623a309d9ca15db9276da30b2129be9c" + }`), + expectedPrincipal: aws.String("*"), + expectedPrincipalOrgID: nil, + }, + { + subject: "AuthType AWS_IAM with Principal OrgID", + statementJSON: []byte(`{ + "Sid": "lambroll-622ed5c2bb0714ef0af1929fcea568e4ba0c4dbe", + "Effect": "Allow", + "Principal": "*", + "Action": "lambda:InvokeFunctionUrl", + "Resource": "arn:aws:lambda:ap-northeast-1:1234567890:function:hello", + "Condition": { + "StringEquals": { + "lambda:FunctionUrlAuthType": "AWS_IAM", + "aws:PrincipalOrgID": "o-xxxxxxxxxx" + } + } + }`), + expectedPrincipal: aws.String("*"), + expectedPrincipalOrgID: aws.String("o-xxxxxxxxxx"), + }, + { + subject: "AuthType AWS_IAM with Principal", + statementJSON: []byte(`{ + "Action": "lambda:InvokeFunctionUrl", + "Condition": { + "StringEquals": { + "lambda:FunctionUrlAuthType": "AWS_IAM" + } + }, + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::123456789012:root" + }, + "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:hello", + "Sid": "lambroll-3b135eca4b14335775cda9f947966093a57d270f" + }`), + expectedPrincipal: aws.String("123456789012"), + expectedPrincipalOrgID: nil, + }, +} + +func TestParseStatement(t *testing.T) { + for _, c := range permissonsTestCases { + st := &lambroll.PolicyStatement{} + if err := json.Unmarshal(c.statementJSON, st); err != nil { + t.Errorf("%s failed to unmarshal json: %s", c.subject, err) + } + if diff := deep.Equal(c.expectedPrincipal, st.PrincipalAccountID()); diff != nil { + t.Errorf("%s PrincipalAccountID diff %s", c.subject, diff) + } + if diff := deep.Equal(c.expectedPrincipalOrgID, st.PrincipalOrgID()); diff != nil { + t.Errorf("%s PrincipalOrgID diff %s", c.subject, diff) + } + } +} diff --git a/go.mod b/go.mod index d8aec4c..f23ddcb 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,19 @@ module github.com/fujiwara/lambroll go 1.20 require ( - github.com/Songmu/prompter v0.5.0 github.com/aereal/jsondiff v0.2.4-0.20221231034809-2e797076aee1 + github.com/Songmu/prompter v0.5.1 github.com/alecthomas/kong v0.8.0 - github.com/aws/aws-sdk-go-v2 v1.21.0 - github.com/aws/aws-sdk-go-v2/config v1.18.38 - github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 - github.com/fatih/color v1.13.0 - github.com/fujiwara/logutils v1.1.0 + github.com/aws/aws-sdk-go-v2 v1.24.0 + github.com/aws/aws-sdk-go-v2/config v1.26.1 + github.com/aws/aws-sdk-go-v2/service/lambda v1.49.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 + github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 + github.com/fatih/color v1.16.0 + github.com/fujiwara/logutils v1.1.2 github.com/fujiwara/ssm-lookup v0.0.1 - github.com/fujiwara/tfstate-lookup v1.1.4 - github.com/go-test/deep v1.0.7 + github.com/fujiwara/tfstate-lookup v1.1.5 + github.com/go-test/deep v1.1.0 github.com/google/go-cmp v0.5.9 github.com/google/go-jsonnet v0.20.0 github.com/hashicorp/go-envparse v0.0.0-20200406174449-d9cfd743a15e @@ -23,14 +23,15 @@ require ( github.com/itchyny/gojq v0.12.11 github.com/kayac/go-config v0.6.0 github.com/kylelemons/godebug v1.1.0 - github.com/mattn/go-isatty v0.0.17 + github.com/mattn/go-isatty v0.0.20 github.com/olekukonko/tablewriter v0.0.5 - github.com/shogo82148/go-retry v1.1.0 - golang.org/x/sys v0.11.0 - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + github.com/samber/lo v1.38.1 + github.com/shogo82148/go-retry v1.1.1 + golang.org/x/sys v0.14.0 ) require ( + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect cloud.google.com/go v0.81.0 // indirect cloud.google.com/go/storage v1.15.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 // indirect @@ -46,22 +47,22 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/BurntSushi/toml v0.3.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.36 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.54 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.13.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 // indirect - github.com/aws/smithy-go v1.14.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.44.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect @@ -78,7 +79,7 @@ require ( github.com/itchyny/timefmt-go v0.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jstemmer/go-junit-report v0.9.1 // indirect - github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect @@ -86,6 +87,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.14.0 // indirect diff --git a/go.sum b/go.sum index 8886c13..b991a3c 100644 --- a/go.sum +++ b/go.sum @@ -74,72 +74,54 @@ github.com/Songmu/prompter v0.5.0 h1:uf60xlFItY5nW+rlLJ2XIUfaUReo4gUEeftuUeHpio8 github.com/Songmu/prompter v0.5.0/go.mod h1:S4Eg25l60kPlnfB2ttFVpvBKYw7RKJexzB3gzpAansY= github.com/aereal/jsondiff v0.2.4-0.20221231034809-2e797076aee1 h1:PrNsETLYKHeiniM9hCru9topK9bOiPjxl5xdYKLQoNo= github.com/aereal/jsondiff v0.2.4-0.20221231034809-2e797076aee1/go.mod h1:a5oayUivaiGHWcodFYZmy0km36ZbSfYtjuVMfKUQdbM= +github.com/Songmu/prompter v0.5.1 h1:IAsttKsOZWSDw7bV1mtGn9TAmLFAjXbp9I/eYmUUogo= +github.com/Songmu/prompter v0.5.1/go.mod h1:CS3jEPD6h9IaLaG6afrl1orTgII9+uDWuw95dr6xHSw= github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0= github.com/alecthomas/kong v0.8.0 h1:ryDCzutfIqJPnNn0omnrgHLbAggDQM2VWHikE1xqK7s= github.com/alecthomas/kong v0.8.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= -github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= -github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= -github.com/aws/aws-sdk-go-v2/config v1.18.14/go.mod h1:0pI6JQBHKwd0JnwAZS3VCapLKMO++UL2BOkWwyyzTnA= -github.com/aws/aws-sdk-go-v2/config v1.18.38 h1:CByQCELMgm2tM1lAehx3XNg0R/pfeXsYzqn0Aq2chJQ= -github.com/aws/aws-sdk-go-v2/config v1.18.38/go.mod h1:vNm9Hf5VgG2fSUWhT3zFrqN/RosGcabFMYgiSoxKFU8= -github.com/aws/aws-sdk-go-v2/credentials v1.13.14/go.mod h1:85ckagDuzdIOnZRwws1eLKnymJs3ZM1QwVC1XcuNGOY= -github.com/aws/aws-sdk-go-v2/credentials v1.13.36 h1:ps0cPswZjpsOk6sLwG6fdXTzrYjCplgPEyG3OUbbdqE= -github.com/aws/aws-sdk-go-v2/credentials v1.13.36/go.mod h1:sY2phUzxbygoyDtTXhqi7GjGjCQ1S5a5Rj8u3ksBxCg= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23/go.mod h1:mOtmAg65GT1HIL/HT/PynwPbS+UG0BgCZ6vhkPqnxWo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.54 h1:u4Cyifho7bnp6NeTCS8zAuxqzycHla4PSJvwXlU8ELI= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.54/go.mod h1:a8gjZYNkBoxPMaA4mIoBT1M+4rOAcJUgFeaxVopMS+k= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30/go.mod h1:vsbq62AOBwQ1LJ/GWKFxX8beUEYeRp/Agitrxee2/qM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 h1:GPUcE/Yq7Ur8YSUk6lVkoIMWnJNO0HT18GUzCWCgCI0= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.21/go.mod h1:QtIEat7ksHH8nFItljyvMI0dGj8lipK2XZ4PhNihTEU= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 h1:wscW+pnn3J1OYnanMnza5ZVYXLX4cKk5rAvUAl4Qu+c= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26/go.mod h1:MtYiox5gvyB+OyP0Mr0Sm/yzbEAIPL9eijj/ouHAPw0= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.24/go.mod h1:QelGeWBVRh9PbbXsfXKTFlU9FjT6W2yP+dW5jMQzOkg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 h1:zZSLP3v3riMOP14H7b4XP0uyfREDQOYv2cqIrvTXDNQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29/go.mod h1:z7EjRjVwZ6pWcWdI2H64dKttvzaP99jRIj5hphW0M5U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23/go.mod h1:9uPh+Hrz2Vn6oMnQYiUi/zbh3ovbnQk19YKINkQny44= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.23/go.mod h1:FJhZWVWBCcgAF8jbep7pxQ1QUsjzTwa9tvEXGw2TDRo= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 h1:dBL3StFxHtpBzJJ/mNEsjXVgfO+7jR0dAIEwLqMapEA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3/go.mod h1:f1QyiAsvIv4B49DmCqrhlXqyaR+0IxMmyX+1P+AnzOM= -github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0 h1:xzyM5ZR9kZW0/Bkw5EiihOy6B+BYclp5K+yb6OHjc7s= -github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0/go.mod h1:Q8zQi5nZpjUF/H55dKEpKfEvFWJkgZzjjqvDb2AR5b4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.30.4/go.mod h1:Dze3kNt4T+Dgb8YCfuIFSBLmE6hadKNxqfdF0Xmqz1I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0 h1:lEmQ1XSD9qLk+NZXbgvLJI/IiTz7OIR2TYUTFH25EI4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0/go.mod h1:aVbf0sko/TsLWHx30c/uVu7c62+0EAJ3vbxaJga0xCw= -github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5 h1:s9QR0F1W5+11lq04OJ/mihpRpA2VDFIHmu+ktgAbNfg= -github.com/aws/aws-sdk-go-v2/service/ssm v1.37.5/go.mod h1:JjBzoceyKkpQY3v1GPIdg6kHqUFHRJ7SDlwtwoH0Qh8= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.3/go.mod h1:jtLIhd+V+lft6ktxpItycqHqiVXrPIRjWIsFIlzMriw= -github.com/aws/aws-sdk-go-v2/service/sso v1.13.6 h1:2PylFCfKCEDv6PeSN09pC/VUiRd10wi1VfHG5FrW0/g= -github.com/aws/aws-sdk-go-v2/service/sso v1.13.6/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.3/go.mod h1:zVwRrfdSmbRZWkUkWjOItY7SOalnFnq/Yg2LVPqDjwc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5 h1:dnInJb4S0oy8aQuri1mV6ipLlnZPfnsDNB9BGO9PDNY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.5/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= -github.com/aws/aws-sdk-go-v2/service/sts v1.18.4/go.mod h1:1mKZHLLpDMHTNSYPJ7qrcnCQdHCWsNQaT0xRvq2u80s= -github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 h1:CQBFElb0LS8RojMJlxRSo/HXipvTZW2S44Lt9Mk2aYQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.21.5/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= -github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk= +github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= +github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o= +github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg= +github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU= +github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM= +github.com/aws/aws-sdk-go-v2/service/lambda v1.49.5 h1:ZHVbzOnoj5nXxUug8iWzqg2Tmp6Jc4CE5tPfoE96qrs= +github.com/aws/aws-sdk-go-v2/service/lambda v1.49.5/go.mod h1:0V5z1X/8NA9eQ5cZSz5ZaHU8xA/hId2ZAlsHeO7Jrdk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.5 h1:5SI5O2tMp/7E/FqhYnaKdxbWjlCi2yujjNI/UO725iU= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.5/go.mod h1:uXndCJoDO9gpuK24rNWVCnrGNUydKFEAYAZ7UU9S0rQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -161,19 +143,21 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fujiwara/logutils v1.1.0 h1:JAYmqW40d/ZjzouB01sfZiaTxwNe4hwmB6lLajZqm1s= -github.com/fujiwara/logutils v1.1.0/go.mod h1:pdb/Uk70rjQWEmFm/OvYH7OG8meZt1fEIqC0qZbvro4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fujiwara/logutils v1.1.2 h1:nYVRyTj+5SyCvpZUrYIZU4kubqNycGTxFXMKJBKe0Sg= +github.com/fujiwara/logutils v1.1.2/go.mod h1:pdb/Uk70rjQWEmFm/OvYH7OG8meZt1fEIqC0qZbvro4= github.com/fujiwara/ssm-lookup v0.0.1 h1:qh3KKSg7QSHYpodeg/gkOQZT+8XjpDUFHhcyH/pHE1k= github.com/fujiwara/ssm-lookup v0.0.1/go.mod h1:YGhJjvcVrHMVdFU5DttcU7Ft9epNwoS0hZbbnry91s0= github.com/fujiwara/tfstate-lookup v1.1.4 h1:oF+cdE0K+Pk9HUxmCzt7jtdhhQS3GfRLg9sj/n8Zx5s= github.com/fujiwara/tfstate-lookup v1.1.4/go.mod h1:G+sFc6osVH71L32pX3+2ibfdhqePPrDZa0ren/QaMYs= +github.com/fujiwara/tfstate-lookup v1.1.5 h1:dNvtfSqSES0y3V7KprcRu7aThUOlRLzvB3mxCFSPeMo= +github.com/fujiwara/tfstate-lookup v1.1.5/go.mod h1:G+sFc6osVH71L32pX3+2ibfdhqePPrDZa0ren/QaMYs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= -github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -224,7 +208,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g= @@ -298,12 +281,11 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -321,9 +303,11 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/shogo82148/go-retry v1.1.0 h1:eNBmDFO0ekhF7GEx8Ie5fUmoqH/O1pSIcdivdBPLMcw= -github.com/shogo82148/go-retry v1.1.0/go.mod h1:NCS+03WD76BaTewJNwgyj3ukqcwNTodfy1SWHmbLZaU= +github.com/shogo82148/go-retry v1.1.1 h1:BfUEVHTNDSjYxoRPC+c/ht5Sy6qdwl+0kFhhubeh4Fo= +github.com/shogo82148/go-retry v1.1.1/go.mod h1:TPSFDcc2rlx2D/yfhi8BBOlsHhVBjjJoMvxG7iFHUbI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -361,6 +345,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -461,7 +447,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -483,18 +468,20 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210616045830-e2b7044e8c71/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.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.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/init.go b/init.go index 1be0278..922fe61 100644 --- a/init.go +++ b/init.go @@ -20,12 +20,15 @@ type InitOption struct { FunctionName *string `help:"Function name for init" required:"true" default:""` DownloadZip bool `help:"Download function.zip" default:"false"` Jsonnet bool `default:"false" help:"render function.json as jsonnet"` + Qualifier *string `help:"function version or alias"` + FunctionURL bool `help:"create function url definition file" default:"false"` } // Init initializes function.json func (app *App) Init(ctx context.Context, opt *InitOption) error { res, err := app.lambda.GetFunction(ctx, &lambda.GetFunctionInput{ FunctionName: opt.FunctionName, + Qualifier: opt.Qualifier, }) var c *types.FunctionConfiguration exists := true @@ -61,15 +64,19 @@ func (app *App) Init(ctx context.Context, opt *InitOption) error { arn := app.functionArn(ctx, *c.FunctionName) log.Printf("[debug] listing tags of %s", arn) res, err := app.lambda.ListTags(ctx, &lambda.ListTagsInput{ - Resource: aws.String(arn), + Resource: aws.String(arn), // tags are not supported for alias }) if err != nil { - return fmt.Errorf("faled to list tags: %w", err) + return fmt.Errorf("failed to list tags: %w", err) } tags = res.Tags } - fn := newFunctionFrom(c, res.Code, tags) + var code *types.FunctionCodeLocation + if res != nil { + code = res.Code + } + fn := newFunctionFrom(c, code, tags) if opt.DownloadZip && res.Code != nil && *res.Code.RepositoryType == "S3" { log.Printf("[info] downloading %s", FunctionZipFilename) @@ -102,7 +109,17 @@ func (app *App) Init(ctx context.Context, opt *InitOption) error { return err } } - return app.saveFile(name, b, os.FileMode(0644)) + if err := app.saveFile(name, b, os.FileMode(0644)); err != nil { + return err + } + + if opt.FunctionURL { + if err := app.initFunctionURL(ctx, fn, opt); err != nil { + return err + } + } + + return nil } func download(url, path string) error { diff --git a/lambroll.go b/lambroll.go index 1c8ac0a..75324a8 100644 --- a/lambroll.go +++ b/lambroll.go @@ -40,7 +40,7 @@ var retryPolicy = retry.Policy{ type Function = lambda.CreateFunctionInput // Tags represents tags of function -type Tags = map[string]string +type Tags map[string]string func (app *App) functionArn(ctx context.Context, name string) string { return fmt.Sprintf( @@ -61,6 +61,11 @@ var ( "function.jsonnet", } + DefaultFunctionURLFilenames = []string{ + "function_url.json", + "function_url.jsonnet", + } + // FunctionZipFilename defines file name for zip archive downloaded at init. FunctionZipFilename = "function.zip" @@ -69,6 +74,8 @@ var ( IgnoreFilename, DefaultFunctionFilenames[0], DefaultFunctionFilenames[1], + DefaultFunctionURLFilenames[0], + DefaultFunctionURLFilenames[1], FunctionZipFilename, ".git/*", ".terraform/*", @@ -195,16 +202,16 @@ func (app *App) AWSAccountID(ctx context.Context) string { svc := sts.NewFromConfig(app.awsConfig) r, err := svc.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}) if err != nil { - log.Println("[warn] failed to get caller identity", err) + log.Println("[warn] failed to get caller identity.", err) return "" } app.accountID = *r.Account return app.accountID } -func (app *App) loadFunction(path string) (*Function, error) { +func loadDefinitionFile[T any](app *App, path string, defaults []string) (*T, error) { if path == "" { - p, err := FindFunctionFile("") + p, err := findDefinitionFile("", defaults) if err != nil { return nil, err } @@ -238,20 +245,28 @@ func (app *App) loadFunction(path string) (*Function, error) { return nil, err } } - var fn Function - if err := unmarshalJSON(src, &fn, path); err != nil { + var v T + if err := unmarshalJSON(src, &v, path); err != nil { return nil, fmt.Errorf("failed to load %s: %w", path, err) } - return &fn, nil + return &v, nil +} + +func (app *App) loadFunction(path string) (*Function, error) { + return loadDefinitionFile[Function](app, path, DefaultFunctionFilenames) } func newFunctionFrom(c *types.FunctionConfiguration, code *types.FunctionCodeLocation, tags Tags) *Function { + if c == nil { + return &Function{} + } fn := &Function{ Architectures: c.Architectures, Description: c.Description, EphemeralStorage: c.EphemeralStorage, FunctionName: c.FunctionName, Handler: c.Handler, + LoggingConfig: c.LoggingConfig, MemorySize: c.MemorySize, Role: c.Role, Runtime: c.Runtime, @@ -313,6 +328,12 @@ func fillDefaultValues(fn *Function) { if fn.MemorySize == nil { fn.MemorySize = aws.Int32(128) } + if fn.LoggingConfig == nil { + fn.LoggingConfig = &types.LoggingConfig{ + LogFormat: types.LogFormatText, + LogGroup: aws.String(resolveLogGroup(fn)), + } + } if fn.TracingConfig == nil { fn.TracingConfig = &types.TracingConfig{ Mode: types.TracingModePassThrough, @@ -377,7 +398,7 @@ func validateUpdateFunction(currentConf *types.FunctionConfiguration, currentCod } // current=Image - if currentCode != nil && currentCode.ImageUri != nil || currentConf.PackageType == types.PackageTypeImage { + if currentCode != nil && currentCode.ImageUri != nil || currentConf != nil && currentConf.PackageType == types.PackageTypeImage { // new=Zip if newCode == nil || newCode.ImageUri == nil { return errCannotUpdateImageAndZip diff --git a/list.go b/list.go index 7d943a6..7e502ab 100644 --- a/list.go +++ b/list.go @@ -31,7 +31,7 @@ func (app *App) List(ctx context.Context, opt *ListOption) error { Resource: aws.String(arn), }) if err != nil { - return fmt.Errorf("faled to list tags: %w", err) + return fmt.Errorf("failed to list tags: %w", err) } b, _ := marshalJSON(newFunctionFrom(&c, nil, res.Tags)) os.Stdout.Write(b) diff --git a/logs.go b/logs.go index f7e106d..b8f318e 100644 --- a/logs.go +++ b/logs.go @@ -23,7 +23,7 @@ func (app *App) Logs(ctx context.Context, opt *LogsOption) error { return fmt.Errorf("failed to load function: %w", err) } - logGroup := "/aws/lambda/" + *fn.FunctionName + logGroup := resolveLogGroup(fn) command := []string{ "aws", "--profile", app.profile, diff --git a/render.go b/render.go index 626e841..d2ef9fa 100644 --- a/render.go +++ b/render.go @@ -7,7 +7,8 @@ import ( ) type RenderOption struct { - Jsonnet bool `default:"false" help:"render function.json as jsonnet"` + Jsonnet bool `default:"false" help:"render function.json as jsonnet"` + FunctionURL string `help:"render function-url definiton file" default:"" env:"LAMBROLL_FUNCTION_URL"` } // Invoke invokes function @@ -16,10 +17,23 @@ func (app *App) Render(ctx context.Context, opt *RenderOption) error { if err != nil { return fmt.Errorf("failed to load function: %w", err) } - b, err := marshalJSON(fn) - if err != nil { - return fmt.Errorf("failed to marshal function: %w", err) + var b []byte + if opt.FunctionURL != "" { + fu, err := app.loadFunctionUrl(opt.FunctionURL, *fn.FunctionName) + if err != nil { + return fmt.Errorf("failed to load function-url: %w", err) + } + b, err = marshalJSON(fu) + if err != nil { + return fmt.Errorf("failed to marshal function-url: %w", err) + } + } else { + b, err = marshalJSON(fn) + if err != nil { + return fmt.Errorf("failed to marshal function: %w", err) + } } + if opt.Jsonnet { b, err = jsonToJsonnet(b, app.functionFilePath) if err != nil { diff --git a/status.go b/status.go new file mode 100644 index 0000000..85742a5 --- /dev/null +++ b/status.go @@ -0,0 +1,96 @@ +package lambroll + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/lambda" + "github.com/aws/aws-sdk-go-v2/service/lambda/types" + "github.com/olekukonko/tablewriter" +) + +// StatusOption represents options for Status() +type StatusOption struct { + Qualifier *string `help:"compare with"` + Output string `help:"output format" default:"table" enum:"table,json"` +} + +type StatusOutput struct { + FunctionName string `json:"FunctionName"` + FunctionArn string `json:"FunctionArn"` + Version string `json:"Version"` + Runtime string `json:"Runtime,omitempty"` + PackageType string `json:"PackageType"` + State string `json:"State"` + LastUpdateState string `json:"LastUpdateState"` + FunctionURL string `json:"FunctionURL,omitempty"` +} + +func (o *StatusOutput) String() string { + buf := new(strings.Builder) + w := tablewriter.NewWriter(buf) + w.Append([]string{"FunctionName", o.FunctionName}) + w.Append([]string{"FunctionArn", o.FunctionArn}) + w.Append([]string{"Version", o.Version}) + if o.Runtime != "" { + w.Append([]string{"Runtime", o.Runtime}) + } + w.Append([]string{"PackageType", o.PackageType}) + w.Append([]string{"State", o.State}) + w.Append([]string{"LastUpdateState", o.LastUpdateState}) + if o.FunctionURL != "" { + w.Append([]string{"FunctionURL", o.FunctionURL}) + } + w.Render() + return buf.String() +} + +// Status prints status of function +func (app *App) Status(ctx context.Context, opt *StatusOption) error { + fn, err := app.loadFunction(app.functionFilePath) + if err != nil { + return fmt.Errorf("failed to load function: %w", err) + } + name := *fn.FunctionName + + res, err := app.lambda.GetFunction(ctx, &lambda.GetFunctionInput{ + FunctionName: &name, + Qualifier: opt.Qualifier, + }) + if err != nil { + return fmt.Errorf("failed to GetFunction %s: %w", name, err) + } + out := &StatusOutput{ + FunctionName: aws.ToString(res.Configuration.FunctionName), + FunctionArn: aws.ToString(res.Configuration.FunctionArn), + Version: aws.ToString(res.Configuration.Version), + Runtime: string(res.Configuration.Runtime), + PackageType: string(res.Configuration.PackageType), + State: string(res.Configuration.State), + LastUpdateState: string(res.Configuration.LastUpdateStatus), + } + if res, err := app.lambda.GetFunctionUrlConfig(ctx, &lambda.GetFunctionUrlConfigInput{ + FunctionName: &name, + Qualifier: opt.Qualifier, + }); err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + // do nothing + } else { + return fmt.Errorf("failed to GetFunctionUrlConfig %s: %w", name, err) + } + } else { + out.FunctionURL = aws.ToString(res.FunctionUrl) + } + switch opt.Output { + case "table": + fmt.Print(out.String()) + case "json": + b, _ := marshalJSON(out) + fmt.Print(string(b)) + } + return nil +} diff --git a/tags_test.go b/tags_test.go index 84e7442..73480b5 100644 --- a/tags_test.go +++ b/tags_test.go @@ -7,7 +7,7 @@ import ( "github.com/go-test/deep" ) -type tags = map[string]string +type tags = lambroll.Tags type keys = []string type tagsTestCase struct { oldTags tags diff --git a/test/function.json b/test/function.json index 280b9f5..ad81f7b 100644 --- a/test/function.json +++ b/test/function.json @@ -21,6 +21,12 @@ } ], "Handler": "index.js", + "LoggingConfig": { + "ApplicationLogLevel": "DEBUG", + "LogFormat": "JSON", + "LogGroup": "/aws/lambda/{{ must_env `FUNCTION_NAME` }}/json", + "SystemLogLevel": "INFO" + }, "MemorySize": 128, "Role": "{{ tfstate `data.aws_iam_role.lambda.arn` }}", "Runtime": "nodejs16.x", diff --git a/test/function.jsonnet b/test/function.jsonnet index e656e9f..5abf6f9 100644 --- a/test/function.jsonnet +++ b/test/function.jsonnet @@ -4,13 +4,13 @@ ], Description: std.extVar('Description'), EphemeralStorage: { - Size: 1024 + Size: 1024, }, Environment: { Variables: { JSON: '{{ env `JSON` | json_escape }}', PREFIXED_TFSTATE_1: '{{ prefix1_tfstate `data.aws_iam_role.lambda.arn` }}', - PREFIXED_TFSTATE_2: '{{ prefix2_tfstate `data.aws_iam_role.lambda.arn` }}' + PREFIXED_TFSTATE_2: '{{ prefix2_tfstate `data.aws_iam_role.lambda.arn` }}', }, }, FunctionName: '{{ must_env `FUNCTION_NAME` }}', @@ -21,6 +21,12 @@ }, ], Handler: 'index.js', + LoggingConfig: { + ApplicationLogLevel: 'DEBUG', + LogFormat: 'JSON', + LogGroup: '/aws/lambda/{{ must_env `FUNCTION_NAME` }}/json', + SystemLogLevel: 'INFO', + }, MemorySize: std.extVar('MemorySize'), Role: '{{ tfstate `data.aws_iam_role.lambda.arn` }}', Runtime: 'nodejs12.x', diff --git a/utils.go b/utils.go index 474c73f..4c0e5c2 100644 --- a/utils.go +++ b/utils.go @@ -66,7 +66,7 @@ func unmarshalJSON(src []byte, v interface{}, path string) error { return nil } -func FindFunctionFile(preffered string) (string, error) { +func findDefinitionFile(preffered string, defaults []string) (string, error) { if preffered != "" { if _, err := os.Stat(preffered); err == nil { return preffered, nil @@ -74,7 +74,7 @@ func FindFunctionFile(preffered string) (string, error) { return "", err } } - for _, name := range DefaultFunctionFilenames { + for _, name := range defaults { if _, err := os.Stat(name); err == nil { return name, nil } @@ -89,3 +89,17 @@ func jsonToJsonnet(src []byte, filepath string) ([]byte, error) { } return []byte(s), nil } + +func resolveLogGroup(fn *Function) string { + if fn.LoggingConfig != nil && fn.LoggingConfig.LogGroup != nil { + return *fn.LoggingConfig.LogGroup + } + return fmt.Sprintf("/aws/lambda/%s", *fn.FunctionName) +} + +func fullQualifiedFunctionName(name string, qualifier *string) string { + if qualifier != nil { + return name + ":" + *qualifier + } + return name + ":" + versionLatest +} diff --git a/versions.go b/versions.go index faf02e0..fe8fc0d 100644 --- a/versions.go +++ b/versions.go @@ -17,7 +17,7 @@ import ( // VersionsOption represents options for Versions() type VersionsOption struct { - Output string `default:"table" enum:"table,json,tsv" help:"output format"` + Output string `default:"table" enum:"table,json,tsv" help:"output format (table,json,tsv)"` Delete bool `default:"false" help:"delete older versions"` KeepVersions int `default:"0" help:"Number of latest versions to keep. Older versions will be deleted with --delete."` }