Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use structs as a container for Action inputs #17

Merged
merged 17 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 66 additions & 10 deletions action_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,60 @@ import (
"github.com/rs/zerolog/log"
)

// Required Action inputs
var (
inputRepoToken *string = flag.String(inputKeyRepoToken, "", "GITHUB_TOKEN or a Personal Access Token")
inputWorkflowRunID *int64 = flag.Int64(inputKeyWorkflowRunID, 0, "GitHub Actions Workflow Run ID")
inputDestination *string = flag.String(inputKeyDestination, "", "The service to export workflow logs to")
)

// Required inputs for S3

// Required inputs for S3
var (
inputAWSAccessKeyID *string = flag.String(inputKeyAWSAccessKeyID, "", "AWS Access Key ID")
inputAWSSecretAccessKey *string = flag.String(inputKeyAWSSecretAccessKey, "", "AWS Secret Access Key")
inputAWSRegion *string = flag.String(inputKeyAWSRegion, "us-east-1", "AWS Region for the S3 bucket")
inputS3BucketName *string = flag.String(inputKeyS3BucketName, "", "S3 bucket name")
inputS3Key *string = flag.String(inputKeyS3Key, "", "S3 key")
)

// Required inputs for Azure Blob Storage

// Required inputs for Azure Blob Storage
var (
inputAzureStorageAccountName *string = flag.String(inputKeyAzureStorageAccountName, "", "Storage account name")
inputAzureStorageAccountKey *string = flag.String(inputKeyAzureStorageAccountKey, "", "Storage account key")
inputContainerName *string = flag.String(inputKeyContainerName, "", "Azure blob storage container name")
inputBlobName *string = flag.String(inputKeyBlobName, "", "Azure blob name")
)

// S3ActionInputs contains inputs required for the `s3` destination
type S3ActionInputs struct {
awsAccessKeyID string
awsSecretAccessKey string
awsRegion string
bucketName string
key string
}

// BlobStorageActionInputs contains inputs required for the `blobstorage` destination
type BlobStorageActionInputs struct {
storageAccountName string
storageAccountKey string
containerName string
blobName string
}

// ActionInputs contains all the pertinent inputs for this GitHub Action. For a given destination, its corresponding
// struct field (e.g. s3Inputs for the `s3` destination) is assumed to be non-nil.
type ActionInputs struct {
repoToken string
workflowRunID int64
destination string
s3Inputs *S3ActionInputs
blobStorageInputs *BlobStorageActionInputs
}

// validateActionInputs validates input combinations that cannot be checked at the action-level.
// In particular, ensures that the destination is valid and any other inputs required for that destination are present.
func validateActionInputs() error {
func validateActionInputs() (ActionInputs, error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function validateActionInputs has 67 lines of code (exceeds 50 allowed). Consider refactoring.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function validateActionInputs has 68 lines of code (exceeds 50 allowed). Consider refactoring.

var matchedDestination string
for _, destination := range supportedDestinations {
if strings.EqualFold(destination, *inputDestination) {
Expand All @@ -41,16 +71,26 @@ func validateActionInputs() error {
}
}
if matchedDestination == "" {
return fmt.Errorf(
return ActionInputs{}, fmt.Errorf(
"supplied destination %s is invalid. Supported values are: %s",
*inputDestination,
strings.Join(supportedDestinations, ", "),
)
}

var inputFlagsToAssertNotEmpty map[string]string
if matchedDestination == "s3" {
var s3Inputs *S3ActionInputs
var blobStorageInputs *BlobStorageActionInputs

if matchedDestination == AmazonS3Destination {
log.Debug().Msg("Validating Action inputs for S3")
s3Inputs = &S3ActionInputs{
awsAccessKeyID: *inputAWSAccessKeyID,
awsSecretAccessKey: *inputAWSSecretAccessKey,
awsRegion: *inputAWSRegion,
bucketName: *inputS3BucketName,
key: *inputS3Key,
}
inputFlagsToAssertNotEmpty = map[string]string{
inputKeyAWSAccessKeyID: *inputAWSAccessKeyID,
inputKeyAWSSecretAccessKey: *inputAWSSecretAccessKey,
Expand All @@ -60,8 +100,14 @@ func validateActionInputs() error {
}
}

if matchedDestination == "blobstorage" {
if matchedDestination == AzureBlobStorageDestination {
log.Debug().Msg("Validating Action inputs for Blob Storage")
blobStorageInputs = &BlobStorageActionInputs{
storageAccountName: *inputAzureStorageAccountName,
storageAccountKey: *inputAzureStorageAccountKey,
containerName: *inputContainerName,
blobName: *inputBlobName,
}
inputFlagsToAssertNotEmpty = map[string]string{
inputKeyAzureStorageAccountName: *inputAzureStorageAccountName,
inputKeyAzureStorageAccountKey: *inputAzureStorageAccountKey,
Expand All @@ -70,12 +116,22 @@ func validateActionInputs() error {
}
}

var emptyInputs []string
for inputName, inputValue := range inputFlagsToAssertNotEmpty {
if len(inputValue) == 0 {
return fmt.Errorf("the input '%s' is required", inputName)
emptyInputs = append(emptyInputs, inputName)
}
}
if len(emptyInputs) > 0 {
return ActionInputs{}, fmt.Errorf("the following inputs are required: %s", strings.Join(emptyInputs, ", "))
}

log.Debug().Msg("Action input validation was successful")
return nil
return ActionInputs{
repoToken: *inputRepoToken,
workflowRunID: *inputWorkflowRunID,
destination: matchedDestination,
s3Inputs: s3Inputs,
blobStorageInputs: blobStorageInputs,
}, nil
}
62 changes: 46 additions & 16 deletions action_inputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ import (
"github.com/stretchr/testify/assert"
)

func TestValidateActionInputsErrorOnInvalidDestination(t *testing.T) {
flag.Set(inputKeyDestination, "someUnsupportedDestination")
defer flag.Set(inputKeyDestination, "")

err := validateActionInputs()
assert.ErrorContains(t, err, "supplied destination someUnsupportedDestination is invalid")
}

func TestValidateActionInputs(t *testing.T) {
flag.Set(inputKeyRepoToken, "testRepoToken")
flag.Set(inputKeyWorkflowRunID, "123")
Expand All @@ -26,8 +18,18 @@ func TestValidateActionInputs(t *testing.T) {
desc string
shouldSucceed bool
inputValuesByKey map[string]string
want string
wantResult ActionInputs
wantError string
}{
{
desc: "Invalid destination",
shouldSucceed: false,
inputValuesByKey: map[string]string{
inputKeyDestination: "someUnsupportedDestination",
},
wantResult: ActionInputs{},
wantError: "supplied destination someUnsupportedDestination is invalid",
},
{
desc: "S3 destination success case",
shouldSucceed: true,
Expand All @@ -39,7 +41,20 @@ func TestValidateActionInputs(t *testing.T) {
inputKeyS3BucketName: "my-bucket",
inputKeyS3Key: "some/key",
},
want: "",
wantResult: ActionInputs{
repoToken: "testRepoToken",
workflowRunID: 123,
destination: "s3",
blobStorageInputs: nil,
s3Inputs: &S3ActionInputs{
awsAccessKeyID: "abc",
awsSecretAccessKey: "abc",
awsRegion: "someregion",
bucketName: "my-bucket",
key: "some/key",
},
},
wantError: "",
},
{
desc: "S3 destination failure case",
Expand All @@ -48,11 +63,12 @@ func TestValidateActionInputs(t *testing.T) {
inputKeyDestination: "s3",
inputKeyAWSAccessKeyID: "abc",
inputKeyAWSSecretAccessKey: "abc",
inputKeyAWSRegion: "someregion",
// inputKeyAWSRegion intentionally excluded
// inputKeyS3BucketName intentionally excluded
inputKeyS3Key: "some/key",
},
want: inputKeyS3BucketName,
wantResult: ActionInputs{},
wantError: fmt.Sprintf("the following inputs are required: %s, %s", inputKeyAWSRegion, inputKeyS3BucketName),
},
{
desc: "Blob Storage destination success case",
Expand All @@ -64,7 +80,19 @@ func TestValidateActionInputs(t *testing.T) {
inputKeyContainerName: "my-container",
inputKeyBlobName: "logs.zip",
},
want: "",
wantResult: ActionInputs{
repoToken: "testRepoToken",
workflowRunID: 123,
destination: "blobstorage",
s3Inputs: nil,
blobStorageInputs: &BlobStorageActionInputs{
storageAccountName: "mystorageaccount",
storageAccountKey: "myaccesskey",
containerName: "my-container",
blobName: "logs.zip",
},
},
wantError: "",
},
{
desc: "Blob Storage destination failure case",
Expand All @@ -76,7 +104,8 @@ func TestValidateActionInputs(t *testing.T) {
inputKeyContainerName: "my-container",
inputKeyBlobName: "logs.zip",
},
want: inputKeyAzureStorageAccountKey,
wantResult: ActionInputs{},
wantError: fmt.Sprintf("the following inputs are required: %s", inputKeyAzureStorageAccountKey),
},
}

Expand All @@ -87,11 +116,12 @@ func TestValidateActionInputs(t *testing.T) {
defer flag.Set(key, "") // Clean up flags by "unsetting" them after this test
}

err := validateActionInputs()
inputs, err := validateActionInputs()
assert.Equal(t, inputs, tC.wantResult)
if tC.shouldSucceed {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, fmt.Sprintf("the input '%s' is required", tC.want))
assert.ErrorContains(t, err, tC.wantError)
}
})
}
Expand Down
13 changes: 9 additions & 4 deletions azure_blob_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
)

func blobStorageClient() (*azblob.Client, error) {
storageAccountName := *inputAzureStorageAccountName
credential, err := azblob.NewSharedKeyCredential(storageAccountName, *inputAzureStorageAccountKey)
// AzureStorageConfig is a struct containing the info needed to initialize a blob storage client
type AzureStorageConfig struct {
storageAccountName string
storageAccountKey string
}

func blobStorageClient(cfg AzureStorageConfig) (*azblob.Client, error) {
credential, err := azblob.NewSharedKeyCredential(cfg.storageAccountName, cfg.storageAccountKey)
if err != nil {
return nil, err
}

url := fmt.Sprintf("https://%s.blob.core.windows.net/", storageAccountName)
url := fmt.Sprintf("https://%s.blob.core.windows.net/", cfg.storageAccountName)

return azblob.NewClientWithSharedKeyCredential(url, credential, nil)
}
Expand Down
24 changes: 19 additions & 5 deletions constants.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
package main

// Environment variables
// GitHub Actions default env vars reference: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
const (
// GitHub Actions default env vars reference: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
envVarGitHubServerURL string = "GITHUB_SERVER_URL"
envVarRepoOwner string = "GITHUB_REPOSITORY_OWNER"
envVarRepoFullName string = "GITHUB_REPOSITORY"
envVarRunnerDebug string = "RUNNER_DEBUG"
envVarDebug string = "DEBUG"
)

tempFileName string = "logs.zip"
githubDefaultBaseURL string = "https://github.com"

// Action inputs
const (
inputKeyRepoToken string = "repo-token"
inputKeyWorkflowRunID string = "run-id"
inputKeyDestination string = "destination"
)

// S3 Action Inputs
const (
inputKeyAWSAccessKeyID string = "aws-access-key-id"
inputKeyAWSSecretAccessKey string = "aws-secret-access-key"
inputKeyAWSRegion string = "aws-region"
inputKeyS3BucketName string = "s3-bucket-name"
inputKeyS3Key string = "s3-key"
)

// Blob Storage Action Inputs
const (
inputKeyAzureStorageAccountName string = "azure-storage-account-name"
inputKeyAzureStorageAccountKey string = "azure-storage-account-key"
inputKeyContainerName string = "container-name"
inputKeyBlobName string = "blob-name"
)

// Misc constants
const (
AmazonS3Destination string = "s3"
AzureBlobStorageDestination string = "blobstorage"
githubDefaultBaseURL string = "https://github.com"
)

var (
supportedDestinations = []string{"s3", "blobstorage"}
supportedDestinations = []string{AmazonS3Destination, AzureBlobStorageDestination}
)
10 changes: 5 additions & 5 deletions github.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"golang.org/x/oauth2"
)

func githubClient(ctx context.Context) (*github.Client, error) {
func githubClient(ctx context.Context, accessToken string) (*github.Client, error) {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: *inputRepoToken},
&oauth2.Token{AccessToken: accessToken},
)
tc := oauth2.NewClient(ctx, ts)

Expand All @@ -23,11 +23,11 @@ func githubClient(ctx context.Context) (*github.Client, error) {

if serverURL != githubDefaultBaseURL {
log.Debug().Str("serverURL", serverURL).
Msgf("Detected a non-default GITHUB_SERVER_URL value. Using GitHub Enterprise Client.")
Msg("Detected a non-default GITHUB_SERVER_URL value. Using GitHub Enterprise Client")
return github.NewEnterpriseClient(serverURL, serverURL, tc)
}

log.Debug().Msg("Using regular GitHub client.")
log.Debug().Msg("Using regular GitHub client")
return github.NewClient(tc), nil
}

Expand All @@ -37,7 +37,7 @@ func getWorkflowRunLogsURLForRunID(ctx context.Context, client *github.Client, w
if err != nil {
return nil, err
}
repoFullName, err := getRequiredEnv(envVarRepoFullName)
repoFullName, err := getRequiredEnv(envVarRepoFullName) // repoFullName is expected to be in the form: username/repoName
if err != nil {
return nil, err
}
Expand Down
Loading