Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions .github/actions/publish/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:
description: 'Tag to upload artifacts to.'
required: true
outputs:
hashes:
hashes:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These are removing trailing newlines since my editor does that automatically.

description: sha256sum hashes of built artifacts
value: ${{ steps.hash.outputs.hashes }}

Expand All @@ -40,8 +40,8 @@ runs:
env:
GITHUB_TOKEN: ${{ inputs.token }}
HOMEBREW_DEPLOY_KEY: ${{ inputs.homebrew-gh-secret }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did we mean to remove the HOMEBREW_DEPLOY_KEY?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope. Probably a weird merge.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Oh thanks for restoring that

- name: Hash build artifacts for provenance
- name: Hash build artifacts for provenance
id: hash
shell: bash
run: |
run: |
echo "hashes=$(sha256sum dist/*.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"
7 changes: 3 additions & 4 deletions .github/workflows/manual-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ jobs:
id-token: write # Needed to obtain Docker tokens
contents: write # Needed to upload release artifacts
runs-on: ubuntu-latest
outputs:
outputs:
hashes: ${{ steps.publish.outputs.hashes }}
steps:
- uses: actions/checkout@v4
name: Checkout
with:
fetch-depth: 0

- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.1
name: 'Get Docker token'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/global/services/docker/public/username = DOCKER_USERNAME, /global/services/docker/public/token = DOCKER_TOKEN'

- uses: ./.github/actions/publish
id: publish
with:
Expand All @@ -50,4 +50,3 @@ jobs:
base64-subjects: "${{ needs.release-ldcli.outputs.hashes }}"
upload-assets: true
upload-tag-name: ${{ inputs.tag }}

6 changes: 3 additions & 3 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ jobs:
needs: [ release-please ]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
outputs:
outputs:
hashes: ${{ steps.publish.outputs.hashes }}
steps:
- uses: actions/checkout@v4
name: Checkout
with:
fetch-depth: 0

- uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.1
name: 'Get Docker token'
with:
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
ssm_parameter_pairs: '/global/services/docker/public/username = DOCKER_USERNAME, /global/services/docker/public/token = DOCKER_TOKEN'

- uses: ./.github/actions/publish
with:
dry-run: 'false'
Expand Down
2 changes: 2 additions & 0 deletions cmd/cmdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/stretchr/testify/require"

"ldcli/internal/analytics"
"ldcli/internal/environments"
"ldcli/internal/flags"
"ldcli/internal/members"
Expand All @@ -24,6 +25,7 @@ func CallCmd(
args []string,
) ([]byte, error) {
rootCmd, err := NewRootCommand(
&analytics.NoopClient{},
environmentsClient,
flagsClient,
membersClient,
Expand Down
8 changes: 6 additions & 2 deletions cmd/environments/environments.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package environments
import (
"github.com/spf13/cobra"

"ldcli/internal/analytics"
"ldcli/internal/environments"
)

func NewEnvironmentsCmd(client environments.Client) (*cobra.Command, error) {
func NewEnvironmentsCmd(
analyticsTracker analytics.Tracker,
client environments.Client,
) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "environments",
Short: "Make requests (list, create, etc.) on environments",
Long: "Make requests (list, create, etc.) on environments",
}

getCmd, err := NewGetCmd(client)
getCmd, err := NewGetCmd(analyticsTracker, client)
if err != nil {
return nil, err
}
Expand Down
22 changes: 19 additions & 3 deletions cmd/environments/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ import (

"ldcli/cmd/cliflags"
"ldcli/cmd/validators"
"ldcli/internal/analytics"
"ldcli/internal/environments"
)

func NewGetCmd(client environments.Client) (*cobra.Command, error) {
func NewGetCmd(
analyticsTracker analytics.Tracker,
client environments.Client,
) (*cobra.Command, error) {
cmd := &cobra.Command{
Args: validators.Validate(),
Long: "Return an environment",
RunE: runGet(client),
RunE: runGet(analyticsTracker, client),
Short: "Return an environment",
Use: "get",
}
Expand Down Expand Up @@ -45,7 +49,10 @@ func NewGetCmd(client environments.Client) (*cobra.Command, error) {
return cmd, nil
}

func runGet(client environments.Client) func(*cobra.Command, []string) error {
func runGet(
analyticsTracker analytics.Tracker,
client environments.Client,
) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
_ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag))
_ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag))
Expand All @@ -61,6 +68,15 @@ func runGet(client environments.Client) func(*cobra.Command, []string) error {
return err
}

analyticsTracker.SendEvent(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is an example event. We're going to come up with a list of events to track and will implement those in another PR.

viper.GetString(cliflags.AccessTokenFlag),
viper.GetString(cliflags.BaseURIFlag),
"environment_get",
map[string]interface{}{
"key": viper.GetString(cliflags.EnvironmentFlag),
"projectKey": viper.GetString(cliflags.ProjectFlag),
})

fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n")

return nil
Expand Down
7 changes: 5 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import (
flagscmd "ldcli/cmd/flags"
mbrscmd "ldcli/cmd/members"
projcmd "ldcli/cmd/projects"
"ldcli/internal/analytics"
"ldcli/internal/environments"
"ldcli/internal/flags"
"ldcli/internal/members"
"ldcli/internal/projects"
)

func NewRootCommand(
analyticsTracker analytics.Tracker,
environmentsClient environments.Client,
flagsClient flags.Client,
membersClient members.Client,
Expand Down Expand Up @@ -69,7 +71,7 @@ func NewRootCommand(
return nil, err
}

environmentsCmd, err := envscmd.NewEnvironmentsCmd(environmentsClient)
environmentsCmd, err := envscmd.NewEnvironmentsCmd(analyticsTracker, environmentsClient)
if err != nil {
return nil, err
}
Expand All @@ -95,8 +97,9 @@ func NewRootCommand(
return cmd, nil
}

func Execute(version string) {
func Execute(analyticsTracker analytics.Tracker, version string) {
rootCmd, err := NewRootCommand(
analyticsTracker,
environments.NewClient(version),
flags.NewClient(version),
members.NewClient(version),
Expand Down
89 changes: 89 additions & 0 deletions internal/analytics/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package analytics

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
)

type Tracker interface {
SendEvent(
accessToken string,
baseURI string,
eventName string,
properties map[string]interface{},
)
}

type Client struct {
HTTPClient *http.Client
wg sync.WaitGroup
}

// SendEvent makes an async request to track the given event with properties.
func (c *Client) SendEvent(
accessToken string,
baseURI string,
eventName string,
properties map[string]interface{},
) {
input := struct {
Event string `json:"event"`
Properties map[string]interface{} `json:"properties"`
}{
Event: eventName,
Properties: properties,
}

c.wg.Add(1)
body, err := json.Marshal(input)
if err != nil { //nolint:staticcheck
// TODO: log error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We'll probably want to add debugging at some point with a flag to enable it.

c.wg.Done()
return
}

req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v2/tracking", baseURI), bytes.NewBuffer(body))
if err != nil { //nolint:staticcheck
// TODO: log error
c.wg.Done()
return
}

req.Header.Add("Authorization", accessToken)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", "launchdarkly-cli/v0.1.1")
var resp *http.Response
go func() {
resp, err = c.HTTPClient.Do(req)
if err != nil { //nolint:staticcheck
// TODO: log error
}
if resp != nil {
resp.Body.Close()
}

_, err := io.ReadAll(resp.Body)
if err != nil { //nolint:staticcheck
// TODO: log error
}
c.wg.Done()
}()
}

func (a *Client) Wait() {
a.wg.Wait()
}

type NoopClient struct{}

func (c *NoopClient) SendEvent(
accessToken string,
baseURI string,
eventName string,
properties map[string]interface{},
) {
}
15 changes: 13 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package main

import "ldcli/cmd"
import (
"net/http"
"time"

"ldcli/cmd"
"ldcli/internal/analytics"
)

// main.version is set at build time via ldflags by go releaser https://goreleaser.com/cookbooks/using-main.version/
var (
version = "dev"
)

func main() {
cmd.Execute(version)
httpClient := &http.Client{
Timeout: time.Second * 3,
}
analyticsClient := &analytics.Client{HTTPClient: httpClient}
cmd.Execute(analyticsClient, version)
analyticsClient.Wait()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is created here so we can call Wait() to block the analytics request after everything else has finished.

}