Skip to content

Commit

Permalink
feat: service env command
Browse files Browse the repository at this point in the history
  • Loading branch information
l-hellmann committed Jul 6, 2024
1 parent f863f52 commit fbfa4f8
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 135 deletions.
101 changes: 50 additions & 51 deletions go.sum

Large diffs are not rendered by default.

102 changes: 85 additions & 17 deletions src/cmd/serviceEnv.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"text/template"

"github.com/pkg/errors"
"github.com/zeropsio/zcli/src/cmd/scope"
"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/entity"
"github.com/zeropsio/zcli/src/entity/repository"
"github.com/zeropsio/zerops-go/dto/input/body"
"github.com/zeropsio/zerops-go/types"
"github.com/zeropsio/zerops-go/types/enum"
"gopkg.in/yaml.v3"

"github.com/zeropsio/zcli/src/i18n"
Expand All @@ -20,40 +27,101 @@ func serviceEnvCmd() *cmdBuilder.Cmd {
Short(i18n.T(i18n.CmdDescServiceEnv)).
ScopeLevel(scope.Service).
Arg(scope.ServiceArgName, cmdBuilder.OptionalArg()).
StringFlag("name", "", i18n.T(i18n.ServiceEnvNameFlag)).
StringFlag("format", "env", i18n.T(i18n.ServiceEnvFormatFlag)).
BoolFlag("no-secrets", false, i18n.T(i18n.ServiceEnvNoSecretsFlag)).
HelpFlag(i18n.T(i18n.CmdHelpServiceEnv)).
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
var userDataSetup repository.GetUserDataSetup
if cmdData.Params.GetBool("no-secrets") {
userDataSetup.EsFilters(func(filter body.EsFilter) body.EsFilter {
filter.Search = append(filter.Search, body.EsSearchItem{
Name: "type",
Operator: "ne",
Value: types.String(enum.UserDataTypeEnumSecret),
})
return filter
})
}

name := cmdData.Params.GetString("name")
format := cmdData.Params.GetString("format")

userDataList, err := repository.GetUserDataByServiceId(
userData, err := repository.GetUserDataByProjectId(
ctx,
cmdData.RestApiClient,
cmdData.Project,
cmdData.Service.ID,
userDataSetup,
)
if err != nil {
return err
}

switch format {
allEnvs := make(map[string]entity.UserData, len(userData))
for _, env := range userData {
allEnvs[fmt.Sprintf("%s_%s", env.ServiceName, env.Key)] = env
}

envs := make(map[string]entity.UserData)
for key, env := range allEnvs {
c := env.Content.Native()
c = os.Expand(c, func(s string) string {
e, ok := allEnvs[s]
if ok {
return e.Content.Native()
}
return s
})
env.Content = types.NewText(c)
if env.ServiceId == cmdData.Service.ID {
envs[key] = env
}
}

format := cmdData.Params.GetString("format")
formatSplit := strings.SplitN(format, "=", 2)
formatKind := formatSplit[0]

switch formatKind {
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("\t", "\t")
enc.Encode(userDataList)
enc := json.NewEncoder(cmdData.Stdout)
enc.SetIndent("", "\t")
out := make(map[string]string, len(envs))
for _, e := range envs {
out[e.Key.Native()] = e.Content.Native()
}
if err := enc.Encode(out); err != nil {
return err
}
case "yaml":
enc := yaml.NewEncoder(os.Stdout)
enc.Encode(userDataList)
enc := yaml.NewEncoder(cmdData.Stdout)
out := make(map[string]string, len(envs))
for _, e := range envs {
out[e.Key.Native()] = e.Content.Native()
}
if err := enc.Encode(out); err != nil {
return err
}
case "value":
for _, userData := range userDataList {
fmt.Println(userData.Content)
for _, env := range envs {
cmdData.Stdout.Println(env.Content)
}
default:
for _, userData := range userDataList {
fmt.Printf("%s=%s\n", userData.Key, userData.Content)
case "go-template":
if len(formatSplit) < 2 {
return errors.New(i18n.T(i18n.ServiceEnvNoTemplateData))
}
formatTemplate := formatSplit[1]
t, err := template.New("go").Parse(formatTemplate + "\n")
if err != nil {
return err
}
for _, value := range envs {
if err := t.Execute(cmdData.Stdout, value); err != nil {
return err
}
}
case "env":
for _, env := range envs {
cmdData.Stdout.Printf("%s=%s\n", env.Key, env.Content)
}
default:
return errors.New(i18n.T(i18n.ServiceEnvInvalidFormatKind, formatKind))
}

return nil
Expand Down
80 changes: 67 additions & 13 deletions src/entity/repository/userData.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,70 @@ func GetUserDataByServiceIdOrName(
}
}
}
return GetUserDataByServiceId(ctx, restApiClient, project, service.ID)
return GetUserDataByServiceId(ctx, restApiClient, project, service.ID, GetUserDataSetup{})
}

type getUserDataByServiceIdSetup struct {
filters []func(body.EsFilter) body.EsFilter
type EsFilterFunc func(body.EsFilter) body.EsFilter
type EsFilterFuncs []EsFilterFunc

func (fs EsFilterFuncs) apply(esFilter body.EsFilter) body.EsFilter {
for _, f := range fs {
esFilter = f(esFilter)
}
return esFilter
}

type GetUserDataSetup struct {
filters EsFilterFuncs
}

func UserDataSetup(
opts ...options.Option[GetUserDataSetup],
) GetUserDataSetup {
return options.ApplyOptions(opts...)
}

func WithUserDataEsFilters(filters ...EsFilterFunc) options.Option[GetUserDataSetup] {
return func(s *GetUserDataSetup) {
s.filters = append(s.filters, filters...)
}
}

func (s *GetUserDataSetup) EsFilters(filters ...EsFilterFunc) {
s.filters = append(s.filters, filters...)
}

func GetUserDataByProjectId(
ctx context.Context,
restApiClient *zeropsRestApiClient.Handler,
project *entity.Project,
setup GetUserDataSetup,
) ([]entity.UserData, error) {
esFilter := body.EsFilter{
Search: []body.EsSearchItem{
{
Name: "projectId",
Operator: "eq",
Value: project.ID.TypedString(),
},
{
Name: "clientId",
Operator: "eq",
Value: project.OrgId.TypedString(),
},
},
}

return getEsSearchUserData(ctx, restApiClient, setup.filters.apply(esFilter))
}

func GetUserDataByServiceId(
ctx context.Context,
restApiClient *zeropsRestApiClient.Handler,
project *entity.Project,
serviceId uuid.ServiceStackId,
opts ...options.Option[getUserDataByServiceIdSetup],
setup GetUserDataSetup,
) ([]entity.UserData, error) {
setup := options.ApplyOptions(opts...)
esFilter := body.EsFilter{
Search: []body.EsSearchItem{
{
Expand All @@ -80,10 +129,14 @@ func GetUserDataByServiceId(
},
}

for _, f := range setup.filters {
esFilter = f(esFilter)
}
return getEsSearchUserData(ctx, restApiClient, setup.filters.apply(esFilter))
}

func getEsSearchUserData(
ctx context.Context,
restApiClient *zeropsRestApiClient.Handler,
esFilter body.EsFilter,
) ([]entity.UserData, error) {
userDataResponse, err := restApiClient.PostUserDataSearch(ctx, esFilter)
if err != nil {
return nil, err
Expand All @@ -104,10 +157,11 @@ func GetUserDataByServiceId(

func userDataFromEsSearch(userData output.EsUserData) entity.UserData {
return entity.UserData{
ID: userData.Id,
ClientId: userData.ClientId,
ServiceStackId: userData.ServiceStackId,
Key: userData.Key,
Content: userData.Content,
ID: userData.Id,
ClientId: userData.ClientId,
ServiceId: userData.ServiceStackId,
ServiceName: userData.ServiceStackName,
Key: userData.Key,
Content: userData.Content,
}
}
11 changes: 6 additions & 5 deletions src/entity/userData.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
)

type UserData struct {
ID uuid.UserDataId
ClientId uuid.ClientId
ServiceStackId uuid.ServiceStackId
Key types.String
Content types.Text
ID uuid.UserDataId
ClientId uuid.ClientId
ServiceId uuid.ServiceStackId
ServiceName types.String
Key types.String
Content types.Text
}
53 changes: 31 additions & 22 deletions src/i18n/en.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ and your %s.`,
ServiceDeleteFailed: "Service deletion failed",
ServiceDeleted: "Service was deleted",

// service env
CmdHelpServiceEnv: "the service env command.",
CmdDescServiceEnv: "Prints expanded envs of service.",
ServiceEnvNoTemplateData: "No format data supplied, see --help",
ServiceEnvInvalidFormatKind: "Invalid format kind %s",


// service log
CmdHelpServiceLog: "the service log command.",
CmdDescServiceLog: "Get service runtime or build log to stdout.",
Expand Down Expand Up @@ -233,28 +240,30 @@ at https://docs.zerops.io/references/cli for further details.`,
VpnWgQuickIsNotInstalledWindows: "wireguard is not installed, please visit https://www.wireguard.com/install/",

// flags description
RegionFlag: "Choose one of Zerops regions. Use the \"zcli region list\" command to list all Zerops regions.",
RegionUrlFlag: "Zerops region file url.",
BuildVersionName: "Adds a custom version name. Automatically filled if the VERSIONNAME environment variable exists.",
BuildWorkingDir: "Sets a custom working directory. Default working directory is the current directory.",
BuildArchiveFilePath: "If set, zCLI creates a tar.gz archive with the application code in the required path relative\nto the working directory. By default, no archive is created.",
ZeropsYamlLocation: "Sets a custom path to the zerops.yml file relative to the working directory. By default zCLI\nlooks for zerops.yml in the working directory.",
UploadGitFolder: "If set, zCLI the .git folder is also uploaded. By default, the .git folder is ignored.",
OrgIdFlag: "If you have access to more than one organization, you must specify the org ID for which the\nproject is to be created.",
LogLimitFlag: "How many of the most recent log messages will be returned. Allowed interval is <1;1000>.\nDefault value = 100.",
LogMinSeverityFlag: "Returns log messages with requested or higher severity. Set either severity number in the interval\n<0;7> or one of following severity codes:\nEMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG.",
LogMsgTypeFlag: "Select either APPLICATION or WEBSERVER log messages to be returned. Default value = APPLICATION.",
LogShowBuildFlag: "If set, zCLI will return build log messages instead of runtime log messages.",
LogFollowFlag: "If set, zCLI will continuously poll for new log messages. By default, the command will exit\nonce there are no more logs to display. To exit from this mode, use Control-C.",
LogFormatFlag: "The format of returned log messages. Following formats are supported: \nFULL: This is the default format. Messages will be returned in the complete Syslog format. \nSHORT: Returns only timestamp and log message.\nJSON: Messages will be returned as one JSON object.\nJSONSTREAM: Messages will be returned as stream of JSON objects.",
LogFormatTemplateFlag: "Set a custom log format. Can be used only with --format=FULL.\nExample: --formatTemplate=\"{{.timestamp}} {{.severity}} {{.facility}} {{.message}}\".\nSupports standard GoLang template format and functions.",
ConfirmFlag: "If set, zCLI will not ask for confirmation of destructive operations.",
ServiceIdFlag: "If you have access to more than one service, you must specify the service ID for which the\ncommand is to be executed.",
ProjectIdFlag: "If you have access to more than one project, you must specify the project ID for which the\ncommand is to be executed.",
ServiceAddTypeFlag: "If set, zCLI will add new service with given type.",
ServiceAddHaFlag: "If set, zCLI will add new service with HA mode enabled.",
VpnAutoDisconnectFlag: "If set, zCLI will automatically disconnect from the VPN if it is already connected.",
ZeropsYamlSetup: "Choose setup to be used from zerops.yml.",
RegionFlag: "Choose one of Zerops regions. Use the \"zcli region list\" command to list all Zerops regions.",
RegionUrlFlag: "Zerops region file url.",
BuildVersionName: "Adds a custom version name. Automatically filled if the VERSIONNAME environment variable exists.",
BuildWorkingDir: "Sets a custom working directory. Default working directory is the current directory.",
BuildArchiveFilePath: "If set, zCLI creates a tar.gz archive with the application code in the required path relative\nto the working directory. By default, no archive is created.",
ZeropsYamlLocation: "Sets a custom path to the zerops.yml file relative to the working directory. By default zCLI\nlooks for zerops.yml in the working directory.",
UploadGitFolder: "If set, zCLI the .git folder is also uploaded. By default, the .git folder is ignored.",
OrgIdFlag: "If you have access to more than one organization, you must specify the org ID for which the\nproject is to be created.",
LogLimitFlag: "How many of the most recent log messages will be returned. Allowed interval is <1;1000>.\nDefault value = 100.",
LogMinSeverityFlag: "Returns log messages with requested or higher severity. Set either severity number in the interval\n<0;7> or one of following severity codes:\nEMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG.",
LogMsgTypeFlag: "Select either APPLICATION or WEBSERVER log messages to be returned. Default value = APPLICATION.",
LogShowBuildFlag: "If set, zCLI will return build log messages instead of runtime log messages.",
LogFollowFlag: "If set, zCLI will continuously poll for new log messages. By default, the command will exit\nonce there are no more logs to display. To exit from this mode, use Control-C.",
LogFormatFlag: "The format of returned log messages. Following formats are supported: \nFULL: This is the default format. Messages will be returned in the complete Syslog format. \nSHORT: Returns only timestamp and log message.\nJSON: Messages will be returned as one JSON object.\nJSONSTREAM: Messages will be returned as stream of JSON objects.",
LogFormatTemplateFlag: "Set a custom log format. Can be used only with --format=FULL.\nExample: --formatTemplate=\"{{.timestamp}} {{.severity}} {{.facility}} {{.message}}\".\nSupports standard GoLang template format and functions.",
ConfirmFlag: "If set, zCLI will not ask for confirmation of destructive operations.",
ServiceIdFlag: "If you have access to more than one service, you must specify the service ID for which the\ncommand is to be executed.",
ProjectIdFlag: "If you have access to more than one project, you must specify the project ID for which the\ncommand is to be executed.",
ServiceAddTypeFlag: "If set, zCLI will add new service with given type.",
ServiceAddHaFlag: "If set, zCLI will add new service with HA mode enabled.",
ServiceEnvFormatFlag: "Format of env output, possible values [env, json, yaml, value, go-template].\nWhen choosing format 'go-template' supply it with template data like --format 'go-template=export -p {{.Key}}={{.Content}}'.\nUseful template values {{.Key}} {{.Content}} {{.ServiceName}} {{.ServiceId}} {{.ClientId}}",
ServiceEnvNoSecretsFlag: "Don't include secrets in command output.",
VpnAutoDisconnectFlag: "If set, zCLI will automatically disconnect from the VPN if it is already connected.",
ZeropsYamlSetup: "Choose setup to be used from zerops.yml.",

// archiveClient
ArchClientWorkingDirectory: "working directory: %s",
Expand Down
54 changes: 28 additions & 26 deletions src/i18n/i18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ const (
ServiceStarted = "ServiceStarted"

// service env
CmdDescServiceEnv = "CmdDescServiceEnv"
CmdHelpServiceEnv = "CmdHelpServiceEnv"
CmdDescServiceEnv = "CmdDescServiceEnv"
CmdHelpServiceEnv = "CmdHelpServiceEnv"
ServiceEnvNoTemplateData = "ServiceEnvNoTemplateData"
ServiceEnvInvalidFormatKind = "ServiceEnvInvalidFormatKind"

// service stop
CmdHelpServiceStop = "CmdHelpServiceStop"
Expand Down Expand Up @@ -217,30 +219,30 @@ const (
VpnWgQuickIsNotInstalledWindows = "VpnWgQuickIsNotInstalledWindows"

// flags description
ServiceEnvNameFlag = "ServiceEnvNameFlag"
ServiceEnvFormatFlag = "ServiceEnvFormatFlag"
RegionFlag = "RegionFlag"
RegionUrlFlag = "RegionUrlFlag"
BuildVersionName = "BuildVersionName"
BuildWorkingDir = "BuildWorkingDir"
BuildArchiveFilePath = "BuildArchiveFilePath"
ZeropsYamlLocation = "ZeropsYamlLocation"
UploadGitFolder = "UploadGitFolder"
OrgIdFlag = "OrgIdFlag"
LogLimitFlag = "LogLimitFlag"
LogMinSeverityFlag = "LogMinSeverityFlag"
LogMsgTypeFlag = "LogMsgTypeFlag"
LogFollowFlag = "LogFollowFlag"
LogShowBuildFlag = "LogShowBuildFlag"
LogFormatFlag = "LogFormatFlag"
LogFormatTemplateFlag = "LogFormatTemplateFlag"
ConfirmFlag = "ConfirmFlag"
ServiceIdFlag = "ServiceIdFlag"
ProjectIdFlag = "ProjectIdFlag"
ServiceAddTypeFlag = "ServiceAddTypeFlag"
ServiceAddHaFlag = "ServiceAddTypeHa"
VpnAutoDisconnectFlag = "VpnAutoDisconnectFlag"
ZeropsYamlSetup = "ZeropsYamlSetup"
RegionFlag = "RegionFlag"
RegionUrlFlag = "RegionUrlFlag"
BuildVersionName = "BuildVersionName"
BuildWorkingDir = "BuildWorkingDir"
BuildArchiveFilePath = "BuildArchiveFilePath"
ZeropsYamlLocation = "ZeropsYamlLocation"
UploadGitFolder = "UploadGitFolder"
OrgIdFlag = "OrgIdFlag"
LogLimitFlag = "LogLimitFlag"
LogMinSeverityFlag = "LogMinSeverityFlag"
LogMsgTypeFlag = "LogMsgTypeFlag"
LogFollowFlag = "LogFollowFlag"
LogShowBuildFlag = "LogShowBuildFlag"
LogFormatFlag = "LogFormatFlag"
LogFormatTemplateFlag = "LogFormatTemplateFlag"
ConfirmFlag = "ConfirmFlag"
ServiceIdFlag = "ServiceIdFlag"
ProjectIdFlag = "ProjectIdFlag"
ServiceAddTypeFlag = "ServiceAddTypeFlag"
ServiceAddHaFlag = "ServiceAddTypeHa"
ServiceEnvFormatFlag = "ServiceEnvFormatFlag"
ServiceEnvNoSecretsFlag = "ServiceEnvNoSecretsFlag"

Check failure on line 243 in src/i18n/i18n.go

View workflow job for this annotation

GitHub Actions / Build && tests for linux 386

G101: Potential hardcoded credentials (gosec)

Check failure on line 243 in src/i18n/i18n.go

View workflow job for this annotation

GitHub Actions / Build && tests for darwin amd64

G101: Potential hardcoded credentials (gosec)
VpnAutoDisconnectFlag = "VpnAutoDisconnectFlag"
ZeropsYamlSetup = "ZeropsYamlSetup"

// archiveClient
ArchClientWorkingDirectory = "ArchClientWorkingDirectory"
Expand Down
Loading

0 comments on commit fbfa4f8

Please sign in to comment.