Skip to content

Commit

Permalink
Merge pull request #880 from stacklok/table-policy
Browse files Browse the repository at this point in the history
medic: print policies as tables
  • Loading branch information
JAORMX authored Sep 7, 2023
2 parents a1d5ce2 + 8f97a5f commit 306ea60
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 77 deletions.
6 changes: 3 additions & 3 deletions cmd/cli/app/policy/policy_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ within a mediator control plane.`,
return fmt.Errorf("error creating policy: %w", err)
}

out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
table := initializeTable(cmd)
renderPolicyTable(resp.GetPolicy(), table)
table.Render()
return nil
},
}
Expand Down
92 changes: 32 additions & 60 deletions cmd/cli/app/policy/policy_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ package policy
import (
"fmt"
"os"
"time"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand All @@ -43,7 +41,7 @@ mediator control plane.`,
provider := viper.GetString("provider")
format := viper.GetString("output")

if format != app.JSON && format != app.YAML && format != "" {
if format != app.JSON && format != app.YAML && format != app.Table {
return fmt.Errorf("error: invalid format: %s", format)
}

Expand All @@ -56,61 +54,28 @@ mediator control plane.`,
defer cancel()

id := viper.GetInt32("id")
status := util.GetConfigValue("status", "status", cmd, false).(bool)
if status {
resp, err := client.GetPolicyStatusById(ctx, &pb.GetPolicyStatusByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
PolicyId: id,
})
util.ExitNicelyOnError(err, "Error getting policy status")

// print results
if format == "" {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Policy ID", "Policy Name", "Status", "Last updated"})

st := resp.GetPolicyStatus()
row := []string{
fmt.Sprintf("%d", st.PolicyId),
st.PolicyName,
st.PolicyStatus,
st.GetLastUpdated().AsTime().Format(time.RFC3339),
}
table.Append(row)
table.Render()
} else if format == app.JSON {
out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
} else if format == app.YAML {
out, err := util.GetYamlFromProto(resp)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
}
} else {
policy, err := client.GetPolicyById(ctx, &pb.GetPolicyByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
Id: id,
})
util.ExitNicelyOnError(err, "Error getting policy")

if format == app.YAML {
out, err := util.GetYamlFromProto(policy)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
} else {
out, err := util.GetJsonFromProto(policy)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
}
policy, err := client.GetPolicyById(ctx, &pb.GetPolicyByIdRequest{
Context: &pb.Context{
Provider: provider,
// TODO set up group if specified
// Currently it's inferred from the authorization token
},
Id: id,
})
util.ExitNicelyOnError(err, "Error getting policy")

switch format {
case app.YAML:
out, err := util.GetYamlFromProto(policy)
util.ExitNicelyOnError(err, "Error getting yaml from proto")
fmt.Println(out)
case app.JSON:
out, err := util.GetJsonFromProto(policy)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
case app.Table:
p := policy.GetPolicy()
handleGetTableOutput(cmd, p)
}

return nil
Expand All @@ -120,8 +85,7 @@ mediator control plane.`,
func init() {
PolicyCmd.AddCommand(policy_getCmd)
policy_getCmd.Flags().Int32P("id", "i", 0, "ID for the policy to query")
policy_getCmd.Flags().BoolP("status", "s", false, "Only return the status of the policy for all the associated repos")
policy_getCmd.Flags().StringP("output", "o", "", "Output format (json or yaml)")
policy_getCmd.Flags().StringP("output", "o", app.Table, "Output format (json, yaml or table)")
policy_getCmd.Flags().StringP("provider", "p", "github", "Provider for the policy")
// TODO set up group if specified

Expand All @@ -131,3 +95,11 @@ func init() {
}

}

func handleGetTableOutput(cmd *cobra.Command, policy *pb.PipelinePolicy) {
table := initializeTable(cmd)

renderPolicyTable(policy, table)

table.Render()
}
28 changes: 23 additions & 5 deletions cmd/cli/app/policy/policy_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/stacklok/mediator/cmd/cli/app"
"github.com/stacklok/mediator/internal/util"
pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1"
)
Expand Down Expand Up @@ -51,8 +52,12 @@ mediator control plane for an specific group.`,

provider := viper.GetString("provider")

if format != "json" && format != "yaml" {
fmt.Fprintf(os.Stderr, "Error: invalid format: %s\n", format)
switch format {
case app.JSON:
case app.YAML:
case app.Table:
default:
return fmt.Errorf("invalid format: %s", format)
}

resp, err := client.ListPolicies(ctx, &pb.ListPoliciesRequest{
Expand All @@ -66,14 +71,18 @@ mediator control plane for an specific group.`,
return fmt.Errorf("error getting policies: %w", err)
}

if format == "json" {
switch format {
case app.JSON:
out, err := util.GetJsonFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
} else if format == "yaml" {
case app.YAML:
out, err := util.GetYamlFromProto(resp)
util.ExitNicelyOnError(err, "Error getting json from proto")
fmt.Println(out)
case app.Table:
handleListTableOutput(cmd, resp)
return nil
}

// this is unreachable
Expand All @@ -84,7 +93,7 @@ mediator control plane for an specific group.`,
func init() {
PolicyCmd.AddCommand(policy_listCmd)
policy_listCmd.Flags().StringP("provider", "p", "", "Provider to list policies for")
policy_listCmd.Flags().StringP("output", "o", "yaml", "Output format (json or yaml)")
policy_listCmd.Flags().StringP("output", "o", app.Table, "Output format (json, yaml or table)")
// TODO: Take group ID into account
// policy_listCmd.Flags().Int32P("group-id", "g", 0, "group id to list roles for")

Expand All @@ -93,3 +102,12 @@ func init() {
os.Exit(1)
}
}

func handleListTableOutput(cmd *cobra.Command, resp *pb.ListPoliciesResponse) {
table := initializeTable(cmd)

for _, v := range resp.Policies {
renderPolicyTable(v, table)
}
table.Render()
}
125 changes: 125 additions & 0 deletions cmd/cli/app/policy/table_render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// Copyright 2023 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package policy

import (
"fmt"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/structpb"
"gopkg.in/yaml.v2"

"github.com/stacklok/mediator/pkg/entities"
pb "github.com/stacklok/mediator/pkg/generated/protobuf/go/mediator/v1"
)

func initializeTable(cmd *cobra.Command) *tablewriter.Table {
table := tablewriter.NewWriter(cmd.OutOrStdout())
table.SetHeader([]string{"Id", "Name", "Provider", "Entity", "Context", "Rule", "Rule Params", "Rule Definition"})
table.SetRowLine(true)
table.SetRowSeparator("-")
table.SetAutoMergeCellsByColumnIndex([]int{0, 1, 2, 3, 4, 5})
// This is needed for the rule definition and rule parameters
table.SetAutoWrapText(false)

return table
}

func renderPolicyTable(
p *pb.PipelinePolicy,
table *tablewriter.Table,
) {
// repositories
renderEntityRuleSets(p, entities.RepositoryEntity, p.Repository, table)

// build_environments
renderEntityRuleSets(p, entities.BuildEnvironmentEntity, p.BuildEnvironment, table)

// artifacts
renderEntityRuleSets(p, entities.ArtifactEntity, p.Artifact, table)
}

func renderEntityRuleSets(
p *pb.PipelinePolicy,
entType entities.EntityType,
rs []*pb.PipelinePolicy_ContextualRuleSet,
table *tablewriter.Table,
) {
for idx := range rs {
rs := rs[idx]

renderContextualRuleSetTable(p, entType, rs, table)
}
}

func renderContextualRuleSetTable(
p *pb.PipelinePolicy,
entType entities.EntityType,
ctxrs *pb.PipelinePolicy_ContextualRuleSet,
table *tablewriter.Table,
) {
for idx := range ctxrs.Rules {
rule := ctxrs.Rules[idx]

ruleCtx := ""
if ctxrs.Context != nil {
ruleCtx = *ctxrs.Context
}

renderRuleTable(p, entType, ruleCtx, rule, table)
}
}

func renderRuleTable(
p *pb.PipelinePolicy,
entType entities.EntityType,
ruleContext string,
rule *pb.PipelinePolicy_Rule,
table *tablewriter.Table,
) {

params := marshalStructOrEmpty(rule.Params)
def := marshalStructOrEmpty(rule.Def)

row := []string{
fmt.Sprintf("%d", *p.Id),
p.Name,
p.Context.Provider,
entType.String(),
ruleContext,
rule.Type,
params,
def,
}
table.Append(row)
}

func marshalStructOrEmpty(v *structpb.Struct) string {
if v == nil {
return ""
}

m := v.AsMap()

// marhsal as YAML
out, err := yaml.Marshal(m)
if err != nil {
return ""
}

return string(out)
}
3 changes: 3 additions & 0 deletions cmd/cli/app/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const JSON = "json"
// YAML is the yaml format for output
const YAML = "yaml"

// Table is the table format for output
const Table = "table"

// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() {
err := RootCmd.Execute()
Expand Down
18 changes: 9 additions & 9 deletions pkg/entities/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,34 @@ import (
)

// EntityType is the type of entity
type entityType string
type EntityType string

// Entity types as string-like enums. Used in CLI and other user-facing code
const (
// RepositoryEntity is a repository entity
RepositoryEntity entityType = "repository"
RepositoryEntity EntityType = "repository"
// BuildEnvironmentEntity is a build environment entity
BuildEnvironmentEntity entityType = "build_environment"
BuildEnvironmentEntity EntityType = "build_environment"
// ArtifactEntity is an artifact entity
ArtifactEntity entityType = "artifact"
ArtifactEntity EntityType = "artifact"
// UnknownEntity is an explicitly unknown entity
UnknownEntity entityType = "unknown"
UnknownEntity EntityType = "unknown"
)

// String returns the string representation of the entity type
func (e entityType) String() string {
func (e EntityType) String() string {
return string(e)
}

// Enum value maps for Entity.
var (
entityTypeToPb = map[entityType]pb.Entity{
entityTypeToPb = map[EntityType]pb.Entity{
RepositoryEntity: pb.Entity_ENTITY_REPOSITORIES,
BuildEnvironmentEntity: pb.Entity_ENTITY_BUILD_ENVIRONMENTS,
ArtifactEntity: pb.Entity_ENTITY_ARTIFACTS,
UnknownEntity: pb.Entity_ENTITY_UNSPECIFIED,
}
pbToEntityType = map[pb.Entity]entityType{
pbToEntityType = map[pb.Entity]EntityType{
pb.Entity_ENTITY_REPOSITORIES: RepositoryEntity,
pb.Entity_ENTITY_BUILD_ENVIRONMENTS: BuildEnvironmentEntity,
pb.Entity_ENTITY_ARTIFACTS: ArtifactEntity,
Expand All @@ -72,7 +72,7 @@ func IsValidEntity(entity pb.Entity) bool {
// FromString returns the Entity enum from a string. Typically used in CLI
// when constructing a protobuf message
func FromString(entity string) pb.Entity {
et := entityType(strings.ToLower(entity))
et := EntityType(strings.ToLower(entity))
// take advantage of the default value of the map being pb.Entity_ENTITY_UNSPECIFIED
return entityTypeToPb[et]
}
Expand Down

0 comments on commit 306ea60

Please sign in to comment.