Add policy ls (#3753)

This commit is contained in:
Erin Krengel 2020-01-16 12:04:51 -08:00 committed by GitHub
parent 77bcba412e
commit 223e0c5e83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 374 additions and 2 deletions

View file

@ -11,6 +11,8 @@ CHANGELOG
- Add Permalink to output when publishing a Policy Pack.
- Add `pulumi policy ls` and `pulumi policy group ls` to list Policy related resources.
## 1.8.1 (2019-12-20)
- Fix a panic in `pulumi stack select`. [#3687](https://github.com/pulumi/pulumi/pull/3687)

View file

@ -28,6 +28,8 @@ func newPolicyCmd() *cobra.Command {
cmd.AddCommand(newPolicyDisableCmd())
cmd.AddCommand(newPolicyEnableCmd())
cmd.AddCommand(newPolicyGroupCmd())
cmd.AddCommand(newPolicyLsCmd())
cmd.AddCommand(newPolicyNewCmd())
cmd.AddCommand(newPolicyPublishCmd())
cmd.AddCommand(newPolicyRmCmd())

137
cmd/policy_group_ls.go Normal file
View file

@ -0,0 +1,137 @@
// Copyright 2016-2020, Pulumi Corporation.
//
// 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 cmd
import (
"context"
"strconv"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/spf13/cobra"
)
func newPolicyGroupCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "group",
Short: "Manage policy groups",
Args: cmdutil.NoArgs,
}
cmd.AddCommand(newPolicyGroupLsCmd())
return cmd
}
func newPolicyGroupLsCmd() *cobra.Command {
var jsonOut bool
var cmd = &cobra.Command{
Use: "ls [org-name]",
Args: cmdutil.MaximumNArgs(1),
Short: "List all Policy Groups for a Pulumi organization",
Long: "List all Policy Groups for a Pulumi organization",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, cliArgs []string) error {
// Get backend.
b, err := currentBackend(display.Options{Color: cmdutil.GetGlobalColorization()})
if err != nil {
return err
}
// Get organization.
var orgName string
if len(cliArgs) > 0 {
orgName = cliArgs[0]
} else {
orgName, err = b.CurrentUser()
if err != nil {
return err
}
}
// List the Policy Packs for the organization.
ctx := context.Background()
policyGroups, err := b.ListPolicyGroups(ctx, orgName)
if err != nil {
return err
}
if jsonOut {
return formatPolicyGroupsJSON(policyGroups)
}
return formatPolicyGroupsConsole(policyGroups)
}),
}
cmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit output as JSON")
return cmd
}
func formatPolicyGroupsConsole(policyGroups apitype.ListPolicyGroupsResponse) error {
// Header string and formatting options to align columns.
headers := []string{"NAME", "DEFAULT", "ENABLED POLICY PACKS", "STACKS"}
rows := []cmdutil.TableRow{}
for _, group := range policyGroups.PolicyGroups {
// Name column
name := group.Name
// Default column
var defaultGroup string
if group.IsOrgDefault {
defaultGroup = "Y"
} else {
defaultGroup = "N"
}
// Number of enabled Policy Packs column
numPolicyPacks := strconv.Itoa(group.NumEnabledPolicyPacks)
// Number of stacks colum
numStacks := strconv.Itoa(group.NumStacks)
// Render the columns.
columns := []string{name, defaultGroup, numPolicyPacks, numStacks}
rows = append(rows, cmdutil.TableRow{Columns: columns})
}
cmdutil.PrintTable(cmdutil.Table{
Headers: headers,
Rows: rows,
})
return nil
}
// policyGroupsJSON is the shape of the --json output of this command. When --json is passed, we print an array
// of policyGroupsJSON objects. While we can add fields to this structure in the future, we should not change
// existing fields.
type policyGroupsJSON struct {
Name string `json:"name"`
Default bool `json:"default"`
NumPolicyPacks int `json:"numPolicyPacks"`
NumStacks int `json:"numStacks"`
}
func formatPolicyGroupsJSON(policyGroups apitype.ListPolicyGroupsResponse) error {
output := make([]policyGroupsJSON, len(policyGroups.PolicyGroups))
for i, group := range policyGroups.PolicyGroups {
output[i] = policyGroupsJSON{
Name: group.Name,
Default: group.IsOrgDefault,
NumPolicyPacks: group.NumEnabledPolicyPacks,
NumStacks: group.NumStacks,
}
}
return printJSON(output)
}

113
cmd/policy_ls.go Normal file
View file

@ -0,0 +1,113 @@
// Copyright 2016-2020, Pulumi Corporation.
//
// 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 cmd
import (
"context"
"fmt"
"strings"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend/display"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/spf13/cobra"
)
func newPolicyLsCmd() *cobra.Command {
var jsonOut bool
var cmd = &cobra.Command{
Use: "ls [org-name]",
Args: cmdutil.MaximumNArgs(1),
Short: "List all Policy Packs for a Pulumi organization",
Long: "List all Policy Packs for a Pulumi organization",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, cliArgs []string) error {
// Get backend.
b, err := currentBackend(display.Options{Color: cmdutil.GetGlobalColorization()})
if err != nil {
return err
}
// Get organization.
var orgName string
if len(cliArgs) > 0 {
orgName = cliArgs[0]
} else {
orgName, err = b.CurrentUser()
if err != nil {
return err
}
}
// List the Policy Packs for the organization.
ctx := context.Background()
policyPacks, err := b.ListPolicyPacks(ctx, orgName)
if err != nil {
return err
}
if jsonOut {
return formatPolicyPacksJSON(policyPacks)
}
return formatPolicyPacksConsole(policyPacks)
}),
}
cmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit output as JSON")
return cmd
}
func formatPolicyPacksConsole(policyPacks apitype.ListPolicyPacksResponse) error {
// Header string and formatting options to align columns.
headers := []string{"NAME", "VERSIONS"}
rows := []cmdutil.TableRow{}
for _, packs := range policyPacks.PolicyPacks {
// Name column
name := packs.Name
// Versions column
versions := strings.Trim(strings.Replace(fmt.Sprint(packs.Versions), " ", ", ", -1), "[]")
// Render the columns.
columns := []string{name, versions}
rows = append(rows, cmdutil.TableRow{Columns: columns})
}
cmdutil.PrintTable(cmdutil.Table{
Headers: headers,
Rows: rows,
})
return nil
}
// policyPacksJSON is the shape of the --json output of this command. When --json is passed, we print an array
// of policyPacksJSON objects. While we can add fields to this structure in the future, we should not change
// existing fields.
type policyPacksJSON struct {
Name string `json:"name"`
Versions []int `json:"versions"`
}
func formatPolicyPacksJSON(policyPacks apitype.ListPolicyPacksResponse) error {
output := make([]policyPacksJSON, len(policyPacks.PolicyPacks))
for i, pack := range policyPacks.PolicyPacks {
output[i] = policyPacksJSON{
Name: pack.Name,
Versions: pack.Versions,
}
}
return printJSON(output)
}

View file

@ -134,3 +134,30 @@ type PolicyPackMetadata struct {
DisplayName string `json:"displayName"`
Version int `json:"version"`
}
// ListPolicyPacksResponse is the response to list an organization's
// Policy Packs.
type ListPolicyPacksResponse struct {
PolicyPacks []PolicyPackWithVersions `json:"policyPacks"`
}
// PolicyPackWithVersions details the specifics of a Policy Pack and all its available versions.
type PolicyPackWithVersions struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Versions []int `json:"versions"`
}
// ListPolicyGroupsResponse lists a summary of the organization's Policy Groups.
type ListPolicyGroupsResponse struct {
PolicyGroups []PolicyGroupSummary `json:"policyGroups"`
}
// PolicyGroupSummary details the name, applicable stacks and the applied Policy
// Packs for an organization's Policy Group.
type PolicyGroupSummary struct {
Name string `json:"name"`
IsOrgDefault bool `json:"isOrgDefault"`
NumStacks int `json:"numStacks"`
NumEnabledPolicyPacks int `json:"numEnabledPolicyPacks"`
}

View file

@ -120,6 +120,12 @@ type Backend interface {
// GetPolicyPack returns a PolicyPack object tied to this backend, or nil if it cannot be found.
GetPolicyPack(ctx context.Context, policyPack string, d diag.Sink) (PolicyPack, error)
// ListPolicyGroups returns all Policy Groups for an organization in this backend or an error if it cannot be found.
ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error)
// ListPolicyPacks returns all Policy Packs for an organization in this backend, or an error if it cannot be found.
ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error)
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
SupportsOrganizations() bool
// ParseStackReference takes a string representation and parses it to a reference which may be used for other

View file

@ -226,6 +226,14 @@ func (b *localBackend) GetPolicyPack(ctx context.Context, policyPack string,
return nil, fmt.Errorf("File state backend does not support resource policy")
}
func (b *localBackend) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
return apitype.ListPolicyGroupsResponse{}, fmt.Errorf("File state backend does not support resource policy")
}
func (b *localBackend) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
return apitype.ListPolicyPacksResponse{}, fmt.Errorf("File state backend does not support resource policy")
}
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
func (b *localBackend) SupportsOrganizations() bool {
return false

View file

@ -466,6 +466,14 @@ func (b *cloudBackend) GetPolicyPack(ctx context.Context, policyPack string,
cl: client.NewClient(b.CloudURL(), apiToken, d)}, nil
}
func (b *cloudBackend) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
return b.client.ListPolicyGroups(ctx, orgName)
}
func (b *cloudBackend) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
return b.client.ListPolicyPacks(ctx, orgName)
}
// SupportsOrganizations tells whether a user can belong to multiple organizations in this backend.
func (b *cloudBackend) SupportsOrganizations() bool {
return true

View file

@ -97,8 +97,20 @@ func getStackPath(stack StackIdentifier, components ...string) string {
return path.Join(append([]string{prefix}, components...)...)
}
// publishPolicyPackPath returns the API path to for the given organization with the given
// components joined with path separators and appended to the organization root.
// listPolicyGroupsPath returns the path for an API call to the Pulumi service to list the Policy Groups
// in a Pulumi organization.
func listPolicyGroupsPath(orgName string) string {
return fmt.Sprintf("/api/orgs/%s/policygroups", orgName)
}
// listPolicyPacksPath returns the path for an API call to the Pulumi service to list the Policy Packs
// in a Pulumi organization.
func listPolicyPacksPath(orgName string) string {
return fmt.Sprintf("/api/orgs/%s/policypacks", orgName)
}
// publishPolicyPackPath returns the path for an API call to the Pulumi service to publish a new Policy Pack
// in a Pulumi organization.
func publishPolicyPackPath(orgName string) string {
return fmt.Sprintf("/api/orgs/%s/policypacks", orgName)
}
@ -497,6 +509,26 @@ func (pc *Client) StartUpdate(ctx context.Context, update UpdateIdentifier,
return resp.Version, resp.Token, nil
}
// ListPolicyGroups lists all `PolicyGroups` the organization has in the Pulumi service.
func (pc *Client) ListPolicyGroups(ctx context.Context, orgName string) (apitype.ListPolicyGroupsResponse, error) {
var resp apitype.ListPolicyGroupsResponse
err := pc.restCall(ctx, "GET", listPolicyGroupsPath(orgName), nil, nil, &resp)
if err != nil {
return resp, errors.Wrapf(err, "List Policy Groups failed")
}
return resp, nil
}
// ListPolicyPacks lists all `PolicyPack` the organization has in the Pulumi service.
func (pc *Client) ListPolicyPacks(ctx context.Context, orgName string) (apitype.ListPolicyPacksResponse, error) {
var resp apitype.ListPolicyPacksResponse
err := pc.restCall(ctx, "GET", listPolicyPacksPath(orgName), nil, nil, &resp)
if err != nil {
return resp, errors.Wrapf(err, "List Policy Packs failed")
}
return resp, nil
}
// PublishPolicyPack publishes a `PolicyPack` to the Pulumi service. If it's successful, it returns
// the version that was published.
func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string,

View file

@ -84,6 +84,14 @@ func (be *MockBackend) URL() string {
panic("not implemented")
}
func (be *MockBackend) ListPolicyGroups(context.Context, string) (apitype.ListPolicyGroupsResponse, error) {
panic("not implemented")
}
func (be *MockBackend) ListPolicyPacks(context.Context, string) (apitype.ListPolicyPacksResponse, error) {
panic("not implemented")
}
func (be *MockBackend) GetPolicyPack(
ctx context.Context, policyPack string, d diag.Sink) (PolicyPack, error) {

View file

@ -3,6 +3,7 @@
package ints
import (
"encoding/json"
"fmt"
"os"
"strings"
@ -36,8 +37,36 @@ func TestPolicy(t *testing.T) {
os.Setenv("TEST_POLICY_PACK", policyPackName)
e.RunCommand("pulumi", "policy", "publish", orgName)
// Check the policy ls commands.
packsOutput, _ := e.RunCommand("pulumi", "policy", "ls", "--json")
var packs []policyPacksJSON
assertJSON(e, packsOutput, &packs)
groupsOutput, _ := e.RunCommand("pulumi", "policy", "group", "ls", "--json")
var groups []policyGroupsJSON
assertJSON(e, groupsOutput, &groups)
// Enable, Disable and then Delete the Policy Pack.
e.RunCommand("pulumi", "policy", "enable", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
e.RunCommand("pulumi", "policy", "disable", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
e.RunCommand("pulumi", "policy", "rm", fmt.Sprintf("%s/%s", orgName, policyPackName), "1")
}
type policyPacksJSON struct {
Name string `json:"name"`
Versions []int `json:"versions"`
}
type policyGroupsJSON struct {
Name string `json:"name"`
Default bool `json:"default"`
NumPolicyPacks int `json:"numPolicyPacks"`
NumStacks int `json:"numStacks"`
}
func assertJSON(e *ptesting.Environment, out string, respObj interface{}) {
err := json.Unmarshal([]byte(out), &respObj)
if err != nil {
e.Errorf("unable to unmarshal %v", out)
}
}