<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsLambdaAlias() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLambdaAliasCreate,
		Read:   resourceAwsLambdaAliasRead,
		Update: resourceAwsLambdaAliasUpdate,
		Delete: resourceAwsLambdaAliasDelete,

		Schema: map[string]*schema.Schema{
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"function_name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"function_version": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

// resourceAwsLambdaAliasCreate maps to:
// CreateAlias in the API / SDK
func resourceAwsLambdaAliasCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	functionName := d.Get("function_name").(string)
	aliasName := d.Get("name").(string)

	log.Printf("[DEBUG] Creating Lambda alias: alias %s for function %s", aliasName, functionName)

	params := &lambda.CreateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(functionName),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(aliasName),
	}

	aliasConfiguration, err := conn.CreateAlias(params)
	if err != nil {
		return fmt.Errorf("Error creating Lambda alias: %s", err)
	}

	d.SetId(*aliasConfiguration.AliasArn)

	return resourceAwsLambdaAliasRead(d, meta)
}

// resourceAwsLambdaAliasRead maps to:
// GetAlias in the API / SDK
func resourceAwsLambdaAliasRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Fetching Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.GetAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	aliasConfiguration, err := conn.GetAlias(params)
	if err != nil {
		return err
	}

	d.Set("description", aliasConfiguration.Description)
	d.Set("function_version", aliasConfiguration.FunctionVersion)
	d.Set("name", aliasConfiguration.Name)
	d.Set("arn", aliasConfiguration.AliasArn)

	return nil
}

// resourceAwsLambdaAliasDelete maps to:
// DeleteAlias in the API / SDK
func resourceAwsLambdaAliasDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[INFO] Deleting Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.DeleteAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	_, err := conn.DeleteAlias(params)
	if err != nil {
		return fmt.Errorf("Error deleting Lambda alias: %s", err)
	}

	d.SetId("")

	return nil
}

// resourceAwsLambdaAliasUpdate maps to:
// UpdateAlias in the API / SDK
func resourceAwsLambdaAliasUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Updating Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.UpdateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(d.Get("function_name").(string)),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(d.Get("name").(string)),
	}

	_, err := conn.UpdateAlias(params)
	if err != nil {
		return fmt.Errorf("Error updating Lambda alias: %s", err)
	}

	return nil
}
<commit_msg>Drop alias from state file if missing from lambda. (#10759)<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsLambdaAlias() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsLambdaAliasCreate,
		Read:   resourceAwsLambdaAliasRead,
		Update: resourceAwsLambdaAliasUpdate,
		Delete: resourceAwsLambdaAliasDelete,

		Schema: map[string]*schema.Schema{
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"function_name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"function_version": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

// resourceAwsLambdaAliasCreate maps to:
// CreateAlias in the API / SDK
func resourceAwsLambdaAliasCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	functionName := d.Get("function_name").(string)
	aliasName := d.Get("name").(string)

	log.Printf("[DEBUG] Creating Lambda alias: alias %s for function %s", aliasName, functionName)

	params := &lambda.CreateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(functionName),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(aliasName),
	}

	aliasConfiguration, err := conn.CreateAlias(params)
	if err != nil {
		return fmt.Errorf("Error creating Lambda alias: %s", err)
	}

	d.SetId(*aliasConfiguration.AliasArn)

	return resourceAwsLambdaAliasRead(d, meta)
}

// resourceAwsLambdaAliasRead maps to:
// GetAlias in the API / SDK
func resourceAwsLambdaAliasRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Fetching Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.GetAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	aliasConfiguration, err := conn.GetAlias(params)
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "ResourceNotFoundException" && strings.Contains(awsErr.Message(), "Cannot find alias arn") {
				d.SetId("")
				return nil
			}
		}
		return err
	}

	d.Set("description", aliasConfiguration.Description)
	d.Set("function_version", aliasConfiguration.FunctionVersion)
	d.Set("name", aliasConfiguration.Name)
	d.Set("arn", aliasConfiguration.AliasArn)

	return nil
}

// resourceAwsLambdaAliasDelete maps to:
// DeleteAlias in the API / SDK
func resourceAwsLambdaAliasDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[INFO] Deleting Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.DeleteAliasInput{
		FunctionName: aws.String(d.Get("function_name").(string)),
		Name:         aws.String(d.Get("name").(string)),
	}

	_, err := conn.DeleteAlias(params)
	if err != nil {
		return fmt.Errorf("Error deleting Lambda alias: %s", err)
	}

	d.SetId("")

	return nil
}

// resourceAwsLambdaAliasUpdate maps to:
// UpdateAlias in the API / SDK
func resourceAwsLambdaAliasUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).lambdaconn

	log.Printf("[DEBUG] Updating Lambda alias: %s:%s", d.Get("function_name"), d.Get("name"))

	params := &lambda.UpdateAliasInput{
		Description:     aws.String(d.Get("description").(string)),
		FunctionName:    aws.String(d.Get("function_name").(string)),
		FunctionVersion: aws.String(d.Get("function_version").(string)),
		Name:            aws.String(d.Get("name").(string)),
	}

	_, err := conn.UpdateAlias(params)
	if err != nil {
		return fmt.Errorf("Error updating Lambda alias: %s", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package models

import (
	"errors"

	// "fmt"
	"time"

	"github.com/koding/bongo"
)

type NotificationContent struct {
	// unique identifier of NotificationContent
	Id int64 `json:"id"`

	// target of the activity (replied messageId, followed accountId etc.)
	TargetId int64 `json:"targetId,string"   sql:"NOT NULL"`

	// Type of the NotificationContent
	TypeConstant string `json:"typeConstant" sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation date of the NotificationContent
	CreatedAt time.Time `json:"createdAt"`
}

const (
	// NotificationContent Types
	NotificationContent_TYPE_LIKE    = "like"
	NotificationContent_TYPE_COMMENT = "comment"
	NotificationContent_TYPE_MENTION = "mention"
	NotificationContent_TYPE_PM      = "chat"
)

func (n *NotificationContent) FindByTarget() error {
	s := map[string]interface{}{
		"type_constant": n.TypeConstant,
		"target_id":     n.TargetId,
	}
	q := bongo.NewQS(s)

	return n.One(q)
}

// CreateNotification validates notifier instance and creates a new notification
// with actor activity.
func CreateNotificationContent(i Notifier) (*NotificationContent, error) {
	// first check for type constant and target id
	if i.GetType() == "" {
		return nil, errors.New("Type must be set")
	}

	if i.GetTargetId() == 0 {
		return nil, errors.New("TargetId must be set")
	}

	if i.GetActorId() == 0 {
		return nil, errors.New("ActorId must be set")
	}

	nc, err := ensureNotificationContent(i)
	if err != nil {
		return nil, err
	}

	a := NewNotificationActivity()
	a.NotificationContentId = nc.Id
	a.ActorId = i.GetActorId()
	a.MessageId = i.GetMessageId()

	if err := a.Create(); err != nil {
		return nil, err
	}

	return nc, nil
}

// ensureNotificationContent adds caching layer on top of notification content fetching
func ensureNotificationContent(i Notifier) (*NotificationContent, error) {
	// check for previous NotificationContent create if it does not exist (type:comment targetId:messageId)
	nc, err := Cache.NotificationContent.ByTypeConstantAndTargetID(i.GetType(), i.GetTargetId())
	if err == nil {
		return nc, nil
	}

	nc = NewNotificationContent()
	nc.TypeConstant = i.GetType()
	nc.TargetId = i.GetTargetId()
	if err := nc.Create(); err != nil {
		return nil, err
	}

	// after creating the notificationcontent we can set it to cache for future
	// usage
	if err := Cache.NotificationContent.SetToCache(nc); err != nil {
		return nil, err
	}

	return nc, nil
}

// FetchByIds fetches notification contents with given ids
func (n *NotificationContent) FetchByIds(ids []int64) ([]NotificationContent, error) {
	notificationContents := make([]NotificationContent, 0)
	if err := bongo.B.FetchByIds(n, &notificationContents, ids); err != nil {
		return nil, err
	}
	return notificationContents, nil
}

// FetchMapByIds returns NotificationContent map with given ids
func (n *NotificationContent) FetchMapByIds(ids []int64) (map[int64]NotificationContent, error) {
	ncList, err := n.FetchByIds(ids)
	if err != nil {
		return nil, err
	}

	ncMap := make(map[int64]NotificationContent, 0)
	for _, nc := range ncList {
		ncMap[nc.Id] = nc
	}

	return ncMap, nil
}

func (n *NotificationContent) FetchIdsByTargetId(targetId int64) ([]int64, error) {
	var ids []int64

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"target_id": targetId,
		},
		Pluck: "id",
	}

	return ids, n.Some(&ids, query)
}

// CreateNotificationType creates an instance of notifier subclasses
func CreateNotificationContentType(notificationType string) (Notifier, error) {
	switch notificationType {
	case NotificationContent_TYPE_LIKE:
		return NewInteractionNotification(notificationType), nil
	case NotificationContent_TYPE_COMMENT:
		return NewReplyNotification(), nil
	case NotificationContent_TYPE_MENTION:
		return NewMentionNotification(), nil
	default:
		return nil, errors.New("undefined notification type")
	}

}

func (n *NotificationContent) GetContentType() (Notifier, error) {
	return CreateNotificationContentType(n.TypeConstant)
}

func (n *NotificationContent) GetDefinition() string {
	nt, err := CreateNotificationContentType(n.TypeConstant)
	if err != nil {
		return ""
	}

	return nt.GetDefinition()
}
<commit_msg>socialapi: deleteByIds func is added for notificationcontent<commit_after>package models

import (
	"errors"
	"socialapi/models"

	// "fmt"
	"time"

	"github.com/koding/bongo"
)

type NotificationContent struct {
	// unique identifier of NotificationContent
	Id int64 `json:"id"`

	// target of the activity (replied messageId, followed accountId etc.)
	TargetId int64 `json:"targetId,string"   sql:"NOT NULL"`

	// Type of the NotificationContent
	TypeConstant string `json:"typeConstant" sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation date of the NotificationContent
	CreatedAt time.Time `json:"createdAt"`
}

const (
	// NotificationContent Types
	NotificationContent_TYPE_LIKE    = "like"
	NotificationContent_TYPE_COMMENT = "comment"
	NotificationContent_TYPE_MENTION = "mention"
	NotificationContent_TYPE_PM      = "chat"
)

func (n *NotificationContent) FindByTarget() error {
	s := map[string]interface{}{
		"type_constant": n.TypeConstant,
		"target_id":     n.TargetId,
	}
	q := bongo.NewQS(s)

	return n.One(q)
}

// CreateNotification validates notifier instance and creates a new notification
// with actor activity.
func CreateNotificationContent(i Notifier) (*NotificationContent, error) {
	// first check for type constant and target id
	if i.GetType() == "" {
		return nil, errors.New("Type must be set")
	}

	if i.GetTargetId() == 0 {
		return nil, errors.New("TargetId must be set")
	}

	if i.GetActorId() == 0 {
		return nil, errors.New("ActorId must be set")
	}

	nc, err := ensureNotificationContent(i)
	if err != nil {
		return nil, err
	}

	a := NewNotificationActivity()
	a.NotificationContentId = nc.Id
	a.ActorId = i.GetActorId()
	a.MessageId = i.GetMessageId()

	if err := a.Create(); err != nil {
		return nil, err
	}

	return nc, nil
}

// ensureNotificationContent adds caching layer on top of notification content fetching
func ensureNotificationContent(i Notifier) (*NotificationContent, error) {
	// check for previous NotificationContent create if it does not exist (type:comment targetId:messageId)
	nc, err := Cache.NotificationContent.ByTypeConstantAndTargetID(i.GetType(), i.GetTargetId())
	if err == nil {
		return nc, nil
	}

	nc = NewNotificationContent()
	nc.TypeConstant = i.GetType()
	nc.TargetId = i.GetTargetId()
	if err := nc.Create(); err != nil {
		return nil, err
	}

	// after creating the notificationcontent we can set it to cache for future
	// usage
	if err := Cache.NotificationContent.SetToCache(nc); err != nil {
		return nil, err
	}

	return nc, nil
}

// FetchByIds fetches notification contents with given ids
func (n *NotificationContent) FetchByIds(ids []int64) ([]NotificationContent, error) {
	notificationContents := make([]NotificationContent, 0)
	if err := bongo.B.FetchByIds(n, &notificationContents, ids); err != nil {
		return nil, err
	}
	return notificationContents, nil
}

// FetchMapByIds returns NotificationContent map with given ids
func (n *NotificationContent) FetchMapByIds(ids []int64) (map[int64]NotificationContent, error) {
	ncList, err := n.FetchByIds(ids)
	if err != nil {
		return nil, err
	}

	ncMap := make(map[int64]NotificationContent, 0)
	for _, nc := range ncList {
		ncMap[nc.Id] = nc
	}

	return ncMap, nil
}

func (n *NotificationContent) FetchIdsByTargetId(targetId int64) ([]int64, error) {
	var ids []int64

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"target_id": targetId,
		},
		Pluck: "id",
	}

	return ids, n.Some(&ids, query)
}

// CreateNotificationType creates an instance of notifier subclasses
func CreateNotificationContentType(notificationType string) (Notifier, error) {
	switch notificationType {
	case NotificationContent_TYPE_LIKE:
		return NewInteractionNotification(notificationType), nil
	case NotificationContent_TYPE_COMMENT:
		return NewReplyNotification(), nil
	case NotificationContent_TYPE_MENTION:
		return NewMentionNotification(), nil
	default:
		return nil, errors.New("undefined notification type")
	}

}

func (n *NotificationContent) GetContentType() (Notifier, error) {
	return CreateNotificationContentType(n.TypeConstant)
}

func (n *NotificationContent) GetDefinition() string {
	nt, err := CreateNotificationContentType(n.TypeConstant)
	if err != nil {
		return ""
	}

	return nt.GetDefinition()
}

// DeleteByIds deletes the given id of NotificationContent (same with content id)
func (n *NotificationContent) DeleteByIds(ids ...int64) error {
	if len(ids) == 0 {
		return models.ErrIdIsNotSet
	}

	for _, id := range ids {
		nc := NewNotificationContent()
		if err := nc.ById(id); err != nil {
			// our aim is removing data from DB
			// so if record is not found in database
			// we can ignore this RecordNotFound error
			if err != bongo.RecordNotFound {
				return err
			}
		}

		if err := nc.Delete(); err != nil {
			if err != bongo.RecordNotFound {
				return err
			}
		}

	}

	return nil
}
<|endoftext|>


<commit_before>package main

// A simple PACS server. Supports C-STORE, C-FIND, C-MOVE.
//
// Usage: ./sampleserver -dir <directory> -port 11111
//
// It starts a DICOM server and serves files under <directory>.

import (
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"github.com/grailbio/go-dicom"
	"github.com/grailbio/go-dicom/dicomio"
	"github.com/grailbio/go-dicom/dicomtag"
	"github.com/grailbio/go-dicom/dicomuid"
	"github.com/grailbio/go-netdicom"
	"github.com/grailbio/go-netdicom/dimse"
	"v.io/x/lib/vlog"
)

var (
	portFlag     = flag.String("port", "10000", "TCP port to listen to")
	aeFlag       = flag.String("ae", "bogusae", "AE title of this server")
	remoteAEFlag = flag.String("remote-ae", "GBMAC0261:localhost:11112", `
Comma-separated list of remote AEs, in form aetitle:host:port, For example -remote-ae testae:foo.example.com:12345,testae2:bar.example.com:23456.
In this example, a C-GET or C-MOVE request to application entity "testae" will resolve to foo.example.com:12345.`)
	dirFlag = flag.String("dir", ".", `
The directory to locate DICOM files to report in C-FIND, C-MOVE, etc.
Files are searched recursivsely under this directory.
Defaults to '.'.`)
	outputFlag = flag.String("output", "", `
The directory to store files received by C-STORE.
If empty, use <dir>/incoming, where <dir> is the value of the -dir flag.`)

	tlsKeyFlag  = flag.String("tls-key", "", "Sets the private key file. If empty, TLS is disabled.")
	tlsCertFlag = flag.String("tls-cert", "", "File containing TLS cert to be presented to the peer.")
	tlsCAFlag   = flag.String("tls-ca", "", "Optional file containing certs to match against what peers present.")
)

type server struct {
	mu *sync.Mutex

	// Set of dicom files the server manages. Keys are file paths.  Guarded
	// by mu.
	datasets map[string]*dicom.DataSet

	// For generating new unique path in C-STORE. Guarded by mu.
	pathSeq int32
}

func (ss *server) onCStore(
	transferSyntaxUID string,
	sopClassUID string,
	sopInstanceUID string,
	data []byte) dimse.Status {
	ss.mu.Lock()
	defer ss.mu.Unlock()
	ss.pathSeq++
	path := path.Join(*outputFlag, fmt.Sprintf("image%04d.dcm", ss.pathSeq))
	out, err := os.Create(path)
	if err != nil {
		dirPath := filepath.Dir(path)
		err := os.MkdirAll(dirPath, 0755)
		if err != nil {
			return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
		}
		out, err = os.Create(path)
		if err != nil {
			vlog.Errorf("%s: create: %v", path, err)
			return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
		}
	}
	defer func() {
		if out != nil {
			out.Close()
		}
	}()
	e := dicomio.NewEncoderWithTransferSyntax(out, transferSyntaxUID)
	dicom.WriteFileHeader(e,
		[]*dicom.Element{
			dicom.MustNewElement(dicomtag.TransferSyntaxUID, transferSyntaxUID),
			dicom.MustNewElement(dicomtag.MediaStorageSOPClassUID, sopClassUID),
			dicom.MustNewElement(dicomtag.MediaStorageSOPInstanceUID, sopInstanceUID),
		})
	e.WriteBytes(data)
	if err := e.Error(); err != nil {
		vlog.Errorf("%s: write: %v", path, err)
		return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
	}
	err = out.Close()
	out = nil
	if err != nil {
		vlog.Errorf("%s: close %s", path, err)
		return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
	}
	vlog.Infof("C-STORE: Created %v", path)
	// Register the new file in ss.datasets.
	ds, err := dicom.ReadDataSetFromFile(path, dicom.ReadOptions{DropPixelData: true})
	if err != nil {
		vlog.Errorf("%s: failed to parse dicom file: %v", path, err)
	} else {
		ss.datasets[path] = ds
	}
	return dimse.Success
}

// Represents a match.
type filterMatch struct {
	path  string           // DICOM path name
	elems []*dicom.Element // Elements within "ds" that match the filter
}

// "filters" are matching conditions specified in C-{FIND,GET,MOVE}. This
// function returns the list of datasets and their elements that match filters.
func (ss *server) findMatchingFiles(filters []*dicom.Element) ([]filterMatch, error) {
	ss.mu.Lock()
	defer ss.mu.Unlock()

	var matches []filterMatch
	for path, ds := range ss.datasets {
		allMatched := true
		match := filterMatch{path: path}
		for _, filter := range filters {
			ok, elem, err := dicom.Query(ds, filter)
			if err != nil {
				return matches, err
			}
			if !ok {
				vlog.VI(2).Infof("DS: %s: filter %v missed", path, filter)
				allMatched = false
				break
			}
			if elem != nil {
				match.elems = append(match.elems, elem)
			} else {
				elem, err := dicom.NewElement(filter.Tag)
				if err != nil {
					vlog.Error(err)
					return matches, err
				}
				match.elems = append(match.elems, elem)
			}
		}
		if allMatched {
			if len(match.elems) == 0 {
				panic(match)
			}
			matches = append(matches, match)
		}
	}
	return matches, nil
}

func (ss *server) onCFind(
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan netdicom.CFindResult) {
	for _, filter := range filters {
		vlog.Infof("CFind: filter %v", filter)
	}
	vlog.Infof("CFind: transfersyntax: %v, classuid: %v",
		dicomuid.UIDString(transferSyntaxUID),
		dicomuid.UIDString(sopClassUID))
	// Match the filter against every file. This is just for demonstration
	matches, err := ss.findMatchingFiles(filters)
	vlog.Infof("C-FIND: found %d matches, err %v", len(matches), err)
	if err != nil {
		ch <- netdicom.CFindResult{Err: err}
	} else {
		for _, match := range matches {
			vlog.VI(1).Infof("C-FIND resp %s: %v", match.path, match.elems)
			ch <- netdicom.CFindResult{Elements: match.elems}
		}
	}
	close(ch)
}

func (ss *server) onCMoveOrCGet(
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan netdicom.CMoveResult) {
	vlog.Infof("C-MOVE: transfersyntax: %v, classuid: %v",
		dicomuid.UIDString(transferSyntaxUID),
		dicomuid.UIDString(sopClassUID))
	for _, filter := range filters {
		vlog.Infof("C-MOVE: filter %v", filter)
	}

	matches, err := ss.findMatchingFiles(filters)
	vlog.Infof("C-MOVE: found %d matches, err %v", len(matches), err)
	if err != nil {
		ch <- netdicom.CMoveResult{Err: err}
	} else {
		for i, match := range matches {
			vlog.VI(1).Infof("C-MOVE resp %d %s: %v", i, match.path, match.elems)
			// Read the file; the one in ss.datasets lack the PixelData.
			ds, err := dicom.ReadDataSetFromFile(match.path, dicom.ReadOptions{})
			resp := netdicom.CMoveResult{
				Remaining: len(matches) - i - 1,
				Path:      match.path,
			}
			if err != nil {
				resp.Err = err
			} else {
				resp.DataSet = ds
			}
			ch <- resp
		}
	}
}

// Find DICOM files in or under "dir" and read its attributes. The return value
// is a map from a pathname to dicom.Dataset (excluding PixelData).
func listDicomFiles(dir string) (map[string]*dicom.DataSet, error) {
	datasets := make(map[string]*dicom.DataSet)
	readFile := func(path string) {
		if _, ok := datasets[path]; ok {
			return
		}
		ds, err := dicom.ReadDataSetFromFile(path, dicom.ReadOptions{DropPixelData: true})
		if err != nil {
			vlog.Errorf("%s: failed to parse dicom file: %v", path, err)
			return
		}
		vlog.Infof("%s: read dicom file", path)
		datasets[path] = ds
	}
	walkCallback := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			vlog.Errorf("%v: skip file: %v", path, err)
			return nil
		}
		if (info.Mode() & os.ModeDir) != 0 {
			// If a directory contains file "DICOMDIR", all the files in the directory are DICOM files.
			if _, err := os.Stat(filepath.Join(path, "DICOMDIR")); err != nil {
				return nil
			}
			subpaths, err := filepath.Glob(path + "/*")
			if err != nil {
				vlog.Errorf("%v: glob: %v", path, err)
				return nil
			}
			for _, subpath := range subpaths {
				if !strings.HasSuffix(subpath, "DICOMDIR") {
					readFile(subpath)
				}
			}
			return nil
		}
		if strings.HasSuffix(path, ".dcm") {
			readFile(path)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkCallback); err != nil {
		return nil, err
	}
	return datasets, nil
}

func parseRemoteAEFlag(flag string) (map[string]string, error) {
	aeMap := make(map[string]string)
	re := regexp.MustCompile("^([^:]+):(.+)$")
	for _, str := range strings.Split(flag, ",") {
		if str == "" {
			continue
		}
		m := re.FindStringSubmatch(str)
		if m == nil {
			return aeMap, fmt.Errorf("Failed to parse AE spec '%v'", str)
		}
		vlog.VI(1).Infof("Remote AE '%v' -> '%v'", m[1], m[2])
		aeMap[m[1]] = m[2]
	}
	return aeMap, nil
}

func canonicalizeHostPort(addr string) string {
	if !strings.Contains(addr, ":") {
		return ":" + addr
	}
	return addr
}

func main() {
	flag.Parse()
	vlog.ConfigureLibraryLoggerFromFlags()
	port := canonicalizeHostPort(*portFlag)
	if *outputFlag == "" {
		*outputFlag = filepath.Join(*dirFlag, "incoming")
	}
	remoteAEs, err := parseRemoteAEFlag(*remoteAEFlag)
	if err != nil {
		vlog.Fatalf("Failed to parse -remote-ae flag: %v", err)
	}
	datasets, err := listDicomFiles(*dirFlag)
	if err != nil {
		vlog.Fatalf("Failed to list DICOM files in %s: %v", *dirFlag, err)
	}
	ss := server{
		mu:       &sync.Mutex{},
		datasets: datasets,
	}
	vlog.Infof("Listening on %s", port)

	var tlsConfig *tls.Config
	if *tlsKeyFlag != "" {
		cert, err := tls.LoadX509KeyPair(*tlsCertFlag, *tlsKeyFlag)
		if err != nil {
			vlog.Fatal(err)
		}
		tlsConfig := &tls.Config{
			Certificates: []tls.Certificate{cert},
		}
		if *tlsCAFlag != "" {
			ca, err := ioutil.ReadFile(*tlsCAFlag)
			if err != nil {
				vlog.Fatal(err)
			}
			tlsConfig.RootCAs = x509.NewCertPool()
			tlsConfig.RootCAs.AppendCertsFromPEM(ca)
			tlsConfig.BuildNameToCertificate()
		}
	}

	params := netdicom.ServiceProviderParams{
		AETitle:   *aeFlag,
		RemoteAEs: remoteAEs,
		CEcho: func() dimse.Status {
			vlog.Info("Received C-ECHO")
			return dimse.Success
		},
		CFind: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CFindResult) {
			ss.onCFind(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CMove: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CMoveResult) {
			ss.onCMoveOrCGet(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CGet: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CMoveResult) {
			ss.onCMoveOrCGet(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CStore: func(transferSyntaxUID string,
			sopClassUID string,
			sopInstanceUID string,
			data []byte) dimse.Status {
			return ss.onCStore(transferSyntaxUID, sopClassUID, sopInstanceUID, data)
		},
		TLSConfig: tlsConfig,
	}
	sp, err := netdicom.NewServiceProvider(params, port)
	if err != nil {
		panic(err)
	}
	sp.Run()
}
<commit_msg>Fixed: sampleserver for C-{GET,MOVE} forgot to close the channel<commit_after>package main

// A simple PACS server. Supports C-STORE, C-FIND, C-MOVE.
//
// Usage: ./sampleserver -dir <directory> -port 11111
//
// It starts a DICOM server and serves files under <directory>.

import (
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"github.com/grailbio/go-dicom"
	"github.com/grailbio/go-dicom/dicomio"
	"github.com/grailbio/go-dicom/dicomtag"
	"github.com/grailbio/go-dicom/dicomuid"
	"github.com/grailbio/go-netdicom"
	"github.com/grailbio/go-netdicom/dimse"
	"v.io/x/lib/vlog"
)

var (
	portFlag     = flag.String("port", "10000", "TCP port to listen to")
	aeFlag       = flag.String("ae", "bogusae", "AE title of this server")
	remoteAEFlag = flag.String("remote-ae", "GBMAC0261:localhost:11112", `
Comma-separated list of remote AEs, in form aetitle:host:port, For example -remote-ae testae:foo.example.com:12345,testae2:bar.example.com:23456.
In this example, a C-GET or C-MOVE request to application entity "testae" will resolve to foo.example.com:12345.`)
	dirFlag = flag.String("dir", ".", `
The directory to locate DICOM files to report in C-FIND, C-MOVE, etc.
Files are searched recursivsely under this directory.
Defaults to '.'.`)
	outputFlag = flag.String("output", "", `
The directory to store files received by C-STORE.
If empty, use <dir>/incoming, where <dir> is the value of the -dir flag.`)

	tlsKeyFlag  = flag.String("tls-key", "", "Sets the private key file. If empty, TLS is disabled.")
	tlsCertFlag = flag.String("tls-cert", "", "File containing TLS cert to be presented to the peer.")
	tlsCAFlag   = flag.String("tls-ca", "", "Optional file containing certs to match against what peers present.")
)

type server struct {
	mu *sync.Mutex

	// Set of dicom files the server manages. Keys are file paths.  Guarded
	// by mu.
	datasets map[string]*dicom.DataSet

	// For generating new unique path in C-STORE. Guarded by mu.
	pathSeq int32
}

func (ss *server) onCStore(
	transferSyntaxUID string,
	sopClassUID string,
	sopInstanceUID string,
	data []byte) dimse.Status {
	ss.mu.Lock()
	defer ss.mu.Unlock()
	ss.pathSeq++
	path := path.Join(*outputFlag, fmt.Sprintf("image%04d.dcm", ss.pathSeq))
	out, err := os.Create(path)
	if err != nil {
		dirPath := filepath.Dir(path)
		err := os.MkdirAll(dirPath, 0755)
		if err != nil {
			return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
		}
		out, err = os.Create(path)
		if err != nil {
			vlog.Errorf("%s: create: %v", path, err)
			return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
		}
	}
	defer func() {
		if out != nil {
			out.Close()
		}
	}()
	e := dicomio.NewEncoderWithTransferSyntax(out, transferSyntaxUID)
	dicom.WriteFileHeader(e,
		[]*dicom.Element{
			dicom.MustNewElement(dicomtag.TransferSyntaxUID, transferSyntaxUID),
			dicom.MustNewElement(dicomtag.MediaStorageSOPClassUID, sopClassUID),
			dicom.MustNewElement(dicomtag.MediaStorageSOPInstanceUID, sopInstanceUID),
		})
	e.WriteBytes(data)
	if err := e.Error(); err != nil {
		vlog.Errorf("%s: write: %v", path, err)
		return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
	}
	err = out.Close()
	out = nil
	if err != nil {
		vlog.Errorf("%s: close %s", path, err)
		return dimse.Status{Status: dimse.StatusNotAuthorized, ErrorComment: err.Error()}
	}
	vlog.Infof("C-STORE: Created %v", path)
	// Register the new file in ss.datasets.
	ds, err := dicom.ReadDataSetFromFile(path, dicom.ReadOptions{DropPixelData: true})
	if err != nil {
		vlog.Errorf("%s: failed to parse dicom file: %v", path, err)
	} else {
		ss.datasets[path] = ds
	}
	return dimse.Success
}

// Represents a match.
type filterMatch struct {
	path  string           // DICOM path name
	elems []*dicom.Element // Elements within "ds" that match the filter
}

// "filters" are matching conditions specified in C-{FIND,GET,MOVE}. This
// function returns the list of datasets and their elements that match filters.
func (ss *server) findMatchingFiles(filters []*dicom.Element) ([]filterMatch, error) {
	ss.mu.Lock()
	defer ss.mu.Unlock()

	var matches []filterMatch
	for path, ds := range ss.datasets {
		allMatched := true
		match := filterMatch{path: path}
		for _, filter := range filters {
			ok, elem, err := dicom.Query(ds, filter)
			if err != nil {
				return matches, err
			}
			if !ok {
				vlog.VI(2).Infof("DS: %s: filter %v missed", path, filter)
				allMatched = false
				break
			}
			if elem != nil {
				match.elems = append(match.elems, elem)
			} else {
				elem, err := dicom.NewElement(filter.Tag)
				if err != nil {
					vlog.Error(err)
					return matches, err
				}
				match.elems = append(match.elems, elem)
			}
		}
		if allMatched {
			if len(match.elems) == 0 {
				panic(match)
			}
			matches = append(matches, match)
		}
	}
	return matches, nil
}

func (ss *server) onCFind(
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan netdicom.CFindResult) {
	for _, filter := range filters {
		vlog.Infof("CFind: filter %v", filter)
	}
	vlog.Infof("CFind: transfersyntax: %v, classuid: %v",
		dicomuid.UIDString(transferSyntaxUID),
		dicomuid.UIDString(sopClassUID))
	// Match the filter against every file. This is just for demonstration
	matches, err := ss.findMatchingFiles(filters)
	vlog.Infof("C-FIND: found %d matches, err %v", len(matches), err)
	if err != nil {
		ch <- netdicom.CFindResult{Err: err}
	} else {
		for _, match := range matches {
			vlog.VI(1).Infof("C-FIND resp %s: %v", match.path, match.elems)
			ch <- netdicom.CFindResult{Elements: match.elems}
		}
	}
	close(ch)
}

func (ss *server) onCMoveOrCGet(
	transferSyntaxUID string,
	sopClassUID string,
	filters []*dicom.Element,
	ch chan netdicom.CMoveResult) {
	vlog.Infof("C-MOVE: transfersyntax: %v, classuid: %v",
		dicomuid.UIDString(transferSyntaxUID),
		dicomuid.UIDString(sopClassUID))
	for _, filter := range filters {
		vlog.Infof("C-MOVE: filter %v", filter)
	}

	matches, err := ss.findMatchingFiles(filters)
	vlog.Infof("C-MOVE: found %d matches, err %v", len(matches), err)
	if err != nil {
		ch <- netdicom.CMoveResult{Err: err}
	} else {
		for i, match := range matches {
			vlog.VI(1).Infof("C-MOVE resp %d %s: %v", i, match.path, match.elems)
			// Read the file; the one in ss.datasets lack the PixelData.
			ds, err := dicom.ReadDataSetFromFile(match.path, dicom.ReadOptions{})
			resp := netdicom.CMoveResult{
				Remaining: len(matches) - i - 1,
				Path:      match.path,
			}
			if err != nil {
				resp.Err = err
			} else {
				resp.DataSet = ds
			}
			ch <- resp
		}
	}
	close(ch)
}

// Find DICOM files in or under "dir" and read its attributes. The return value
// is a map from a pathname to dicom.Dataset (excluding PixelData).
func listDicomFiles(dir string) (map[string]*dicom.DataSet, error) {
	datasets := make(map[string]*dicom.DataSet)
	readFile := func(path string) {
		if _, ok := datasets[path]; ok {
			return
		}
		ds, err := dicom.ReadDataSetFromFile(path, dicom.ReadOptions{DropPixelData: true})
		if err != nil {
			vlog.Errorf("%s: failed to parse dicom file: %v", path, err)
			return
		}
		vlog.Infof("%s: read dicom file", path)
		datasets[path] = ds
	}
	walkCallback := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			vlog.Errorf("%v: skip file: %v", path, err)
			return nil
		}
		if (info.Mode() & os.ModeDir) != 0 {
			// If a directory contains file "DICOMDIR", all the files in the directory are DICOM files.
			if _, err := os.Stat(filepath.Join(path, "DICOMDIR")); err != nil {
				return nil
			}
			subpaths, err := filepath.Glob(path + "/*")
			if err != nil {
				vlog.Errorf("%v: glob: %v", path, err)
				return nil
			}
			for _, subpath := range subpaths {
				if !strings.HasSuffix(subpath, "DICOMDIR") {
					readFile(subpath)
				}
			}
			return nil
		}
		if strings.HasSuffix(path, ".dcm") {
			readFile(path)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkCallback); err != nil {
		return nil, err
	}
	return datasets, nil
}

func parseRemoteAEFlag(flag string) (map[string]string, error) {
	aeMap := make(map[string]string)
	re := regexp.MustCompile("^([^:]+):(.+)$")
	for _, str := range strings.Split(flag, ",") {
		if str == "" {
			continue
		}
		m := re.FindStringSubmatch(str)
		if m == nil {
			return aeMap, fmt.Errorf("Failed to parse AE spec '%v'", str)
		}
		vlog.VI(1).Infof("Remote AE '%v' -> '%v'", m[1], m[2])
		aeMap[m[1]] = m[2]
	}
	return aeMap, nil
}

func canonicalizeHostPort(addr string) string {
	if !strings.Contains(addr, ":") {
		return ":" + addr
	}
	return addr
}

func main() {
	flag.Parse()
	vlog.ConfigureLibraryLoggerFromFlags()
	port := canonicalizeHostPort(*portFlag)
	if *outputFlag == "" {
		*outputFlag = filepath.Join(*dirFlag, "incoming")
	}
	remoteAEs, err := parseRemoteAEFlag(*remoteAEFlag)
	if err != nil {
		vlog.Fatalf("Failed to parse -remote-ae flag: %v", err)
	}
	datasets, err := listDicomFiles(*dirFlag)
	if err != nil {
		vlog.Fatalf("Failed to list DICOM files in %s: %v", *dirFlag, err)
	}
	ss := server{
		mu:       &sync.Mutex{},
		datasets: datasets,
	}
	vlog.Infof("Listening on %s", port)

	var tlsConfig *tls.Config
	if *tlsKeyFlag != "" {
		cert, err := tls.LoadX509KeyPair(*tlsCertFlag, *tlsKeyFlag)
		if err != nil {
			vlog.Fatal(err)
		}
		tlsConfig := &tls.Config{
			Certificates: []tls.Certificate{cert},
		}
		if *tlsCAFlag != "" {
			ca, err := ioutil.ReadFile(*tlsCAFlag)
			if err != nil {
				vlog.Fatal(err)
			}
			tlsConfig.RootCAs = x509.NewCertPool()
			tlsConfig.RootCAs.AppendCertsFromPEM(ca)
			tlsConfig.BuildNameToCertificate()
		}
	}

	params := netdicom.ServiceProviderParams{
		AETitle:   *aeFlag,
		RemoteAEs: remoteAEs,
		CEcho: func() dimse.Status {
			vlog.Info("Received C-ECHO")
			return dimse.Success
		},
		CFind: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CFindResult) {
			ss.onCFind(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CMove: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CMoveResult) {
			ss.onCMoveOrCGet(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CGet: func(transferSyntaxUID string, sopClassUID string, filter []*dicom.Element, ch chan netdicom.CMoveResult) {
			ss.onCMoveOrCGet(transferSyntaxUID, sopClassUID, filter, ch)
		},
		CStore: func(transferSyntaxUID string,
			sopClassUID string,
			sopInstanceUID string,
			data []byte) dimse.Status {
			return ss.onCStore(transferSyntaxUID, sopClassUID, sopInstanceUID, data)
		},
		TLSConfig: tlsConfig,
	}
	sp, err := netdicom.NewServiceProvider(params, port)
	if err != nil {
		panic(err)
	}
	sp.Run()
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// To run this test locally, you will need to do the following:
// • Navigate to your Google Cloud Project
// • Get a copy of a Service Account Key File for testing (should be in .json format)
// • If you are unable to obtain an existing key file, create one:
//    • > IAM and Admin > Service Accounts
//    • Under the needed Service Account > Actions > Manage Keys
//    • Add Key > Create New Key
//    • Select JSON, and the click Create
// • Look for an available VM Instance, or create one- > Compute > Compute Engine > VM Instances
// • On the VM Instance, click the SSH Button.  Then upload:
//    • Your Service Account Key File
//    • This script, along with setup.sh
//    • A copy of env.conf, containing the required environment variables (see existing skeleton)/
// • Set your environment variables (Usually this will be `source env.conf`)
// • Ensure that your VM is properly set up to run the integration test e.g.
//    • wget -c https://golang.org/dl/go1.15.2.linux-amd64.tar.gz
//       • Check https://golang.org/dl/for the latest version of Go
//    • sudo tar -C /usr/local -xvzf go1.15.2.linux-amd64.tar.gz
//    • go mod init google.golang.org/api/google-api-go-client
//    • go mod tidy
// • Run setup.sh
// • go test -tags integration`

package byoid

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"

	"golang.org/x/oauth2/google"
	"google.golang.org/api/dns/v1"
	"google.golang.org/api/idtoken"
	"google.golang.org/api/option"
)

const (
	envCredentials  = "GOOGLE_APPLICATION_CREDENTIALS"
	envAudienceOIDC = "GCLOUD_TESTS_GOLANG_AUDIENCE_OIDC"
	envAudienceAWS  = "GCLOUD_TESTS_GOLANG_AUDIENCE_AWS"
	envProject      = "GOOGLE_CLOUD_PROJECT"
)

var (
	oidcAudience string
	awsAudience  string
	oidcToken    string
	clientID     string
	projectID    string
)

// TestMain contains all of the setup code that needs to be run once before any of the tests are run
func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Short() {
		// This line runs all of our individual tests
		os.Exit(m.Run())
	}
	keyFileName := os.Getenv(envCredentials)
	if keyFileName == "" {
		log.Fatalf("Please set %s to your keyfile", envCredentials)
	}

	projectID = os.Getenv(envProject)
	if projectID == "" {
		log.Fatalf("Please set %s to the ID of the project", envProject)
	}

	oidcAudience = os.Getenv(envAudienceOIDC)
	if oidcAudience == "" {
		log.Fatalf("Please set %s to the OIDC Audience", envAudienceOIDC)
	}

	awsAudience = os.Getenv(envAudienceAWS)
	if awsAudience == "" {
		log.Fatalf("Please set %s to the AWS Audience", envAudienceAWS)
	}

	var err error

	clientID, err = getClientID(keyFileName)
	if err != nil {
		log.Fatalf("Error getting Client ID: %v", err)
	}

	oidcToken, err = generateGoogleToken(keyFileName)
	if err != nil {
		log.Fatalf("Error generating Google token: %v", err)
	}

	// This line runs all of our individual tests
	os.Exit(m.Run())
}

// keyFile is a struct to extract the relevant json fields for our ServiceAccount KeyFile
type keyFile struct {
	ClientEmail string `json:"client_email"`
	ClientID    string `json:"client_id"`
}

func getClientID(keyFileName string) (string, error) {
	kf, err := os.Open(keyFileName)
	if err != nil {
		return "", err
	}
	defer kf.Close()

	decoder := json.NewDecoder(kf)
	var keyFileSettings keyFile
	if err = decoder.Decode(&keyFileSettings); err != nil {
		return "", err
	}

	return fmt.Sprintf("projects/-/serviceAccounts/%s", keyFileSettings.ClientEmail), nil
}

func generateGoogleToken(keyFileName string) (string, error) {
	ts, err := idtoken.NewTokenSource(context.Background(), oidcAudience, option.WithCredentialsFile(keyFileName))
	if err != nil {
		return "", nil
	}

	token, err := ts.Token()
	if err != nil {
		return "", nil
	}

	return token.AccessToken, nil
}

// testBYOID makes sure that the default credentials works for
// whatever preconditions have been set beforehand
// by using those credentials to run our client libraries.
//
// In each test we will set up whatever preconditions we need,
// and then use this function.
func testBYOID(t *testing.T, c config) {
	t.Helper()

	// Set up config file.
	configFile, err := ioutil.TempFile("", "config.json")
	if err != nil {
		t.Fatalf("Error creating config file: %v", err)
	}
	defer os.Remove(configFile.Name())

	err = json.NewEncoder(configFile).Encode(c)
	if err != nil {
		t.Errorf("Error writing to config file: %v", err)
	}
	configFile.Close()

	// Once the default credentials are obtained,
	// we should be able to access Google Cloud resources.
	dnsService, err := dns.NewService(context.Background(), option.WithCredentialsFile(configFile.Name()))
	if err != nil {
		t.Fatalf("Could not establish DNS Service: %v", err)
	}

	_, err = dnsService.Projects.Get(projectID).Do()
	if err != nil {
		t.Fatalf("DNS Service failed: %v", err)
	}
}

// These structs makes writing our config as json to a file much easier.
type config struct {
	Type                           string           `json:"type"`
	Audience                       string           `json:"audience"`
	SubjectTokenType               string           `json:"subject_token_type"`
	TokenURL                       string           `json:"token_url"`
	ServiceAccountImpersonationURL string           `json:"service_account_impersonation_url"`
	CredentialSource               credentialSource `json:"credential_source"`
}

type credentialSource struct {
	File                        string `json:"file,omitempty"`
	URL                         string `json:"url,omitempty"`
	EnvironmentID               string `json:"environment_id,omitempty"`
	RegionURL                   string `json:"region_url"`
	RegionalCredVerificationURL string `json:"regional_cred_verification_url,omitempty"`
}

// Tests to make sure File based external credentials continues to work.
func TestFileBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	// Set up Token as a file
	tokenFile, err := ioutil.TempFile("", "token.txt")
	if err != nil {
		t.Fatalf("Error creating token file:")
	}
	defer os.Remove(tokenFile.Name())

	tokenFile.WriteString(oidcToken)
	tokenFile.Close()

	// Run our test!
	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       oidcAudience,
		SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
		TokenURL:                       "https://sts.googleapis.com/v1beta/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			File: tokenFile.Name(),
		},
	})
}

// Tests to make sure URL based external credentials work properly.
func TestURLBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	//Set up a server to return a token
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			t.Errorf("Unexpected request method, %v is found", r.Method)
		}
		w.Write([]byte(oidcToken))
	}))

	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       oidcAudience,
		SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
		TokenURL:                       "https://sts.googleapis.com/v1beta/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			URL: ts.URL,
		},
	})
}

// Tests to make sure AWS based external credentials work properly.
func TestAWSBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	data := url.Values{}
	data.Set("audience", clientID)
	data.Set("includeEmail", "true")

	client, err := google.DefaultClient(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		t.Fatalf("Failed to create default client: %v", err)
	}
	resp, err := client.PostForm(fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateIdToken", clientID), data)
	if err != nil {
		t.Fatalf("Failed to generate an ID token: %v", err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("Failed to get Google ID token for AWS test: %v", err)
	}

	var res map[string]interface{}

	if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
		t.Fatalf("Could not successfully parse response from generateIDToken: %v", err)
	}
	token, ok := res["token"]
	if !ok {
		t.Fatalf("Didn't receieve an ID token back from generateIDToken")
	}

	data = url.Values{}
	data.Set("Action", "AssumeRoleWithWebIdentity")
	data.Set("Version", "2011-06-15")
	data.Set("DurationSeconds", "3600")
	data.Set("RoleSessionName", os.Getenv("GCLOUD_TESTS_GOLANG_AWS_ROLE_NAME"))
	data.Set("RoleArn", os.Getenv("GCLOUD_TESTS_GOLANG_AWS_ROLE_ID"))
	data.Set("WebIdentityToken", token.(string))

	resp, err = http.PostForm("https://sts.amazonaws.com/", data)
	if err != nil {
		t.Fatalf("Failed to post data to AWS: %v", err)
	}
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Failed to parse response body from AWS: %v", err)
	}

	var respVars struct {
		SessionToken    string `xml:"AssumeRoleWithWebIdentityResult>Credentials>SessionToken"`
		SecretAccessKey string `xml:"AssumeRoleWithWebIdentityResult>Credentials>SecretAccessKey"`
		AccessKeyID     string `xml:"AssumeRoleWithWebIdentityResult>Credentials>AccessKeyId"`
	}

	if err = xml.Unmarshal(bodyBytes, &respVars); err != nil {
		t.Fatalf("Failed to unmarshal XML response from AWS.")
	}

	if respVars.SessionToken == "" || respVars.SecretAccessKey == "" || respVars.AccessKeyID == "" {
		t.Fatalf("Couldn't find the required variables in the response from the AWS server.")
	}

	currSessTokEnv := os.Getenv("AWS_SESSION_TOKEN")
	defer os.Setenv("AWS_SESSION_TOKEN", currSessTokEnv)
	os.Setenv("AWS_SESSION_TOKEN", respVars.SessionToken)

	currSecAccKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
	defer os.Setenv("AWS_SECRET_ACCESS_KEY", currSecAccKey)
	os.Setenv("AWS_SECRET_ACCESS_KEY", respVars.SecretAccessKey)

	currAccKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
	defer os.Setenv("AWS_ACCESS_KEY_ID", currAccKeyID)
	os.Setenv("AWS_ACCESS_KEY_ID", respVars.AccessKeyID)

	currRegion := os.Getenv("AWS_REGION")
	defer os.Setenv("AWS_REGION", currRegion)
	os.Setenv("AWS_REGION", "us-east-1")

	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       awsAudience,
		SubjectTokenType:               "urn:ietf:params:aws:token-type:aws4_request",
		TokenURL:                       "https://sts.googleapis.com/v1/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			EnvironmentID:               "aws1",
			RegionalCredVerificationURL: "https://sts.us-east-1.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
		},
	})
}
<commit_msg>test: integration Tests for Pluggable Auth (#1607)<commit_after>// Copyright 2021 Google LLC.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// To run this test locally, you will need to do the following:
// • Navigate to your Google Cloud Project
// • Get a copy of a Service Account Key File for testing (should be in .json format)
// • If you are unable to obtain an existing key file, create one:
//    • > IAM and Admin > Service Accounts
//    • Under the needed Service Account > Actions > Manage Keys
//    • Add Key > Create New Key
//    • Select JSON, and the click Create
// • Look for an available VM Instance, or create one- > Compute > Compute Engine > VM Instances
// • On the VM Instance, click the SSH Button.  Then upload:
//    • Your Service Account Key File
//    • This script, along with setup.sh
//    • A copy of env.conf, containing the required environment variables (see existing skeleton)/
// • Set your environment variables (Usually this will be `source env.conf`)
// • Ensure that your VM is properly set up to run the integration test e.g.
//    • wget -c https://golang.org/dl/go1.15.2.linux-amd64.tar.gz
//       • Check https://golang.org/dl/for the latest version of Go
//    • sudo tar -C /usr/local -xvzf go1.15.2.linux-amd64.tar.gz
//    • go mod init google.golang.org/api/google-api-go-client
//    • go mod tidy
// • Run setup.sh
// • go test -tags integration`

package byoid

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"

	"golang.org/x/oauth2/google"
	"google.golang.org/api/dns/v1"
	"google.golang.org/api/idtoken"
	"google.golang.org/api/option"
)

const (
	envCredentials  = "GOOGLE_APPLICATION_CREDENTIALS"
	envAudienceOIDC = "GCLOUD_TESTS_GOLANG_AUDIENCE_OIDC"
	envAudienceAWS  = "GCLOUD_TESTS_GOLANG_AUDIENCE_AWS"
	envProject      = "GOOGLE_CLOUD_PROJECT"
)

var (
	oidcAudience string
	awsAudience  string
	oidcToken    string
	clientID     string
	projectID    string
)

// TestMain contains all of the setup code that needs to be run once before any of the tests are run
func TestMain(m *testing.M) {
	flag.Parse()
	if testing.Short() {
		// This line runs all of our individual tests
		os.Exit(m.Run())
	}
	keyFileName := os.Getenv(envCredentials)
	if keyFileName == "" {
		log.Fatalf("Please set %s to your keyfile", envCredentials)
	}

	projectID = os.Getenv(envProject)
	if projectID == "" {
		log.Fatalf("Please set %s to the ID of the project", envProject)
	}

	oidcAudience = os.Getenv(envAudienceOIDC)
	if oidcAudience == "" {
		log.Fatalf("Please set %s to the OIDC Audience", envAudienceOIDC)
	}

	awsAudience = os.Getenv(envAudienceAWS)
	if awsAudience == "" {
		log.Fatalf("Please set %s to the AWS Audience", envAudienceAWS)
	}

	var err error

	clientID, err = getClientID(keyFileName)
	if err != nil {
		log.Fatalf("Error getting Client ID: %v", err)
	}

	oidcToken, err = generateGoogleToken(keyFileName)
	if err != nil {
		log.Fatalf("Error generating Google token: %v", err)
	}

	// This line runs all of our individual tests
	os.Exit(m.Run())
}

// keyFile is a struct to extract the relevant json fields for our ServiceAccount KeyFile
type keyFile struct {
	ClientEmail string `json:"client_email"`
	ClientID    string `json:"client_id"`
}

func getClientID(keyFileName string) (string, error) {
	kf, err := os.Open(keyFileName)
	if err != nil {
		return "", err
	}
	defer kf.Close()

	decoder := json.NewDecoder(kf)
	var keyFileSettings keyFile
	if err = decoder.Decode(&keyFileSettings); err != nil {
		return "", err
	}

	return fmt.Sprintf("projects/-/serviceAccounts/%s", keyFileSettings.ClientEmail), nil
}

func generateGoogleToken(keyFileName string) (string, error) {
	ts, err := idtoken.NewTokenSource(context.Background(), oidcAudience, option.WithCredentialsFile(keyFileName))
	if err != nil {
		return "", nil
	}

	token, err := ts.Token()
	if err != nil {
		return "", nil
	}

	return token.AccessToken, nil
}

// testBYOID makes sure that the default credentials works for
// whatever preconditions have been set beforehand
// by using those credentials to run our client libraries.
//
// In each test we will set up whatever preconditions we need,
// and then use this function.
func testBYOID(t *testing.T, c config) {
	t.Helper()

	// Set up config file.
	configFile, err := ioutil.TempFile("", "config.json")
	if err != nil {
		t.Fatalf("Error creating config file: %v", err)
	}
	defer os.Remove(configFile.Name())

	err = json.NewEncoder(configFile).Encode(c)
	if err != nil {
		t.Errorf("Error writing to config file: %v", err)
	}
	configFile.Close()

	// Once the default credentials are obtained,
	// we should be able to access Google Cloud resources.
	dnsService, err := dns.NewService(context.Background(), option.WithCredentialsFile(configFile.Name()))
	if err != nil {
		t.Fatalf("Could not establish DNS Service: %v", err)
	}

	_, err = dnsService.Projects.Get(projectID).Do()
	if err != nil {
		t.Fatalf("DNS Service failed: %v", err)
	}
}

// These structs makes writing our config as json to a file much easier.
type config struct {
	Type                           string           `json:"type"`
	Audience                       string           `json:"audience"`
	SubjectTokenType               string           `json:"subject_token_type"`
	TokenURL                       string           `json:"token_url"`
	ServiceAccountImpersonationURL string           `json:"service_account_impersonation_url"`
	CredentialSource               credentialSource `json:"credential_source"`
}

type credentialSource struct {
	File                        string           `json:"file,omitempty"`
	URL                         string           `json:"url,omitempty"`
	Executable                  executableConfig `json:"executable,omitempty"`
	EnvironmentID               string           `json:"environment_id,omitempty"`
	RegionURL                   string           `json:"region_url"`
	RegionalCredVerificationURL string           `json:"regional_cred_verification_url,omitempty"`
}

type executableConfig struct {
	Command       string `json:"command"`
	TimeoutMillis int    `json:"timeout_millis,omitempty"`
	OutputFile    string `json:"output_file,omitempty"`
}

// Tests to make sure File based external credentials continues to work.
func TestFileBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	// Set up Token as a file
	tokenFile, err := ioutil.TempFile("", "token.txt")
	if err != nil {
		t.Fatalf("Error creating token file:")
	}
	defer os.Remove(tokenFile.Name())

	tokenFile.WriteString(oidcToken)
	tokenFile.Close()

	// Run our test!
	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       oidcAudience,
		SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
		TokenURL:                       "https://sts.googleapis.com/v1beta/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			File: tokenFile.Name(),
		},
	})
}

// Tests to make sure URL based external credentials work properly.
func TestURLBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	//Set up a server to return a token
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			t.Errorf("Unexpected request method, %v is found", r.Method)
		}
		w.Write([]byte(oidcToken))
	}))

	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       oidcAudience,
		SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
		TokenURL:                       "https://sts.googleapis.com/v1/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			URL: ts.URL,
		},
	})
}

// Tests to make sure AWS based external credentials work properly.
func TestAWSBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}
	data := url.Values{}
	data.Set("audience", clientID)
	data.Set("includeEmail", "true")

	client, err := google.DefaultClient(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
	if err != nil {
		t.Fatalf("Failed to create default client: %v", err)
	}
	resp, err := client.PostForm(fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateIdToken", clientID), data)
	if err != nil {
		t.Fatalf("Failed to generate an ID token: %v", err)
	}
	if resp.StatusCode != 200 {
		t.Fatalf("Failed to get Google ID token for AWS test: %v", err)
	}

	var res map[string]interface{}

	if err = json.NewDecoder(resp.Body).Decode(&res); err != nil {
		t.Fatalf("Could not successfully parse response from generateIDToken: %v", err)
	}
	token, ok := res["token"]
	if !ok {
		t.Fatalf("Didn't receieve an ID token back from generateIDToken")
	}

	data = url.Values{}
	data.Set("Action", "AssumeRoleWithWebIdentity")
	data.Set("Version", "2011-06-15")
	data.Set("DurationSeconds", "3600")
	data.Set("RoleSessionName", os.Getenv("GCLOUD_TESTS_GOLANG_AWS_ROLE_NAME"))
	data.Set("RoleArn", os.Getenv("GCLOUD_TESTS_GOLANG_AWS_ROLE_ID"))
	data.Set("WebIdentityToken", token.(string))

	resp, err = http.PostForm("https://sts.amazonaws.com/", data)
	if err != nil {
		t.Fatalf("Failed to post data to AWS: %v", err)
	}
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("Failed to parse response body from AWS: %v", err)
	}

	var respVars struct {
		SessionToken    string `xml:"AssumeRoleWithWebIdentityResult>Credentials>SessionToken"`
		SecretAccessKey string `xml:"AssumeRoleWithWebIdentityResult>Credentials>SecretAccessKey"`
		AccessKeyID     string `xml:"AssumeRoleWithWebIdentityResult>Credentials>AccessKeyId"`
	}

	if err = xml.Unmarshal(bodyBytes, &respVars); err != nil {
		t.Fatalf("Failed to unmarshal XML response from AWS.")
	}

	if respVars.SessionToken == "" || respVars.SecretAccessKey == "" || respVars.AccessKeyID == "" {
		t.Fatalf("Couldn't find the required variables in the response from the AWS server.")
	}

	currSessTokEnv := os.Getenv("AWS_SESSION_TOKEN")
	defer os.Setenv("AWS_SESSION_TOKEN", currSessTokEnv)
	os.Setenv("AWS_SESSION_TOKEN", respVars.SessionToken)

	currSecAccKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
	defer os.Setenv("AWS_SECRET_ACCESS_KEY", currSecAccKey)
	os.Setenv("AWS_SECRET_ACCESS_KEY", respVars.SecretAccessKey)

	currAccKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
	defer os.Setenv("AWS_ACCESS_KEY_ID", currAccKeyID)
	os.Setenv("AWS_ACCESS_KEY_ID", respVars.AccessKeyID)

	currRegion := os.Getenv("AWS_REGION")
	defer os.Setenv("AWS_REGION", currRegion)
	os.Setenv("AWS_REGION", "us-east-1")

	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       awsAudience,
		SubjectTokenType:               "urn:ietf:params:aws:token-type:aws4_request",
		TokenURL:                       "https://sts.googleapis.com/v1/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			EnvironmentID:               "aws1",
			RegionalCredVerificationURL: "https://sts.us-east-1.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15",
		},
	})
}

// Tests to make sure executable based external credentials continues to work.
// We're using the same setup as file based external account credentials, and using `cat` as the command
func TestExecutableBasedCredentials(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test")
	}

	// Set up Script as a executable file
	scriptFile, err := ioutil.TempFile("", "script.sh")
	if err != nil {
		t.Fatalf("Error creating token file:")
	}
	defer os.Remove(scriptFile.Name())

	fmt.Fprintf(scriptFile, `#!/bin/bash
echo "{\"success\":true,\"version\":1,\"expiration_time\":%v,\"token_type\":\"urn:ietf:params:oauth:token-type:jwt\",\"id_token\":\"%v\"}"`,
		time.Now().Add(time.Hour).Unix(), oidcToken)
	scriptFile.Close()
	os.Chmod(scriptFile.Name(), 0700)

	// Run our test!
	testBYOID(t, config{
		Type:                           "external_account",
		Audience:                       oidcAudience,
		SubjectTokenType:               "urn:ietf:params:oauth:token-type:jwt",
		TokenURL:                       "https://sts.googleapis.com/v1/token",
		ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/%s:generateAccessToken", clientID),
		CredentialSource: credentialSource{
			Executable: executableConfig{
				Command: scriptFile.Name(),
			},
		},
	})
}
<|endoftext|>


<commit_before>package initcmd

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"gopkg.in/yaml.v1"

	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/swag"
)

// Spec a command struct for initializing a new swagger application.
type Spec struct {
	Format      string   `long:"format" description:"the format for the spec document" default:"yaml" choice:"yaml" choice:"json"`
	Title       string   `long:"title" description:"the title of the API"`
	Description string   `long:"description" description:"the description of the API"`
	Version     string   `long:"version" description:"the version of the API" default:"0.1.0"`
	Terms       string   `long:"terms" description:"the terms of services"`
	Consumes    []string `long:"consumes" description:"add a content type to the global consumes definitions, can repeat" default:"application/json"`
	Produces    []string `long:"produces" description:"add a content type to the global produces definitions, can repeat" default:"application/json"`
	Schemes     []string `long:"scheme" description:"add a scheme to the global schemes definition, can repeat" default:"http"`
	Contact     struct {
		Name  string `long:"contact.name" description:"name of the primary contact for the API"`
		URL   string `long:"contact.url" description:"url of the primary contact for the API"`
		Email string `long:"contact.email" description:"email of the primary contact for the API"`
	}
	License struct {
		Name string `long:"license.name" description:"name of the license for the API"`
		URL  string `long:"license.url" description:"url of the license for the API"`
	}
}

// Execute this command
func (s *Spec) Execute(args []string) error {
	targetPath := "."
	if len(args) > 0 {
		targetPath = args[0]
	}
	realPath, err := filepath.Abs(targetPath)
	if err != nil {
		return err
	}
	var file *os.File
	switch s.Format {
	case "json":
		file, err = os.Create(filepath.Join(realPath, "swagger.json"))
		if err != nil {
			return err
		}
	case "yaml", "yml":
		file, err = os.Create(filepath.Join(realPath, "swagger.yml"))
		if err != nil {
			return err
		}
	default:
		return fmt.Errorf("invalid format: %s", s.Format)
	}
	defer file.Close()
	log.Println("creating specification document in", filepath.Join(targetPath, file.Name()))

	var doc spec.Swagger
	info := new(spec.Info)
	doc.Info = info

	doc.Swagger = "2.0"
	doc.Paths = new(spec.Paths)
	doc.Definitions = make(spec.Definitions)

	info.Title = s.Title
	if info.Title == "" {
		info.Title = swag.ToHumanNameTitle(filepath.Base(realPath))
	}
	info.Description = s.Description
	info.Version = s.Version
	info.TermsOfService = s.Terms
	if s.Contact.Name != "" || s.Contact.Email != "" || s.Contact.URL != "" {
		var contact spec.ContactInfo
		contact.Name = s.Contact.Name
		contact.Email = s.Contact.Email
		contact.URL = s.Contact.URL
		info.Contact = &contact
	}
	if s.License.Name != "" || s.License.URL != "" {
		var license spec.License
		license.Name = s.License.Name
		license.URL = s.License.URL
		info.License = &license
	}

	for _, cons := range s.Consumes {
		doc.Consumes = append(doc.Consumes, cons)
	}
	for _, prods := range s.Produces {
		doc.Produces = append(doc.Produces, prods)
	}
	for _, scheme := range s.Schemes {
		doc.Schemes = append(doc.Schemes, scheme)
	}

	if s.Format == "json" {
		enc := json.NewEncoder(file)
		if err := enc.Encode(doc); err != nil {
			return err
		}
		return nil
	}

	b, err := yaml.Marshal(swag.ToDynamicJSON(doc))
	if err != nil {
		return err
	}
	if _, err := file.Write(b); err != nil {
		return err
	}
	return nil
}
<commit_msg>wrong yaml import :(<commit_after>package initcmd

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"gopkg.in/yaml.v2"

	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/swag"
)

// Spec a command struct for initializing a new swagger application.
type Spec struct {
	Format      string   `long:"format" description:"the format for the spec document" default:"yaml" choice:"yaml" choice:"json"`
	Title       string   `long:"title" description:"the title of the API"`
	Description string   `long:"description" description:"the description of the API"`
	Version     string   `long:"version" description:"the version of the API" default:"0.1.0"`
	Terms       string   `long:"terms" description:"the terms of services"`
	Consumes    []string `long:"consumes" description:"add a content type to the global consumes definitions, can repeat" default:"application/json"`
	Produces    []string `long:"produces" description:"add a content type to the global produces definitions, can repeat" default:"application/json"`
	Schemes     []string `long:"scheme" description:"add a scheme to the global schemes definition, can repeat" default:"http"`
	Contact     struct {
		Name  string `long:"contact.name" description:"name of the primary contact for the API"`
		URL   string `long:"contact.url" description:"url of the primary contact for the API"`
		Email string `long:"contact.email" description:"email of the primary contact for the API"`
	}
	License struct {
		Name string `long:"license.name" description:"name of the license for the API"`
		URL  string `long:"license.url" description:"url of the license for the API"`
	}
}

// Execute this command
func (s *Spec) Execute(args []string) error {
	targetPath := "."
	if len(args) > 0 {
		targetPath = args[0]
	}
	realPath, err := filepath.Abs(targetPath)
	if err != nil {
		return err
	}
	var file *os.File
	switch s.Format {
	case "json":
		file, err = os.Create(filepath.Join(realPath, "swagger.json"))
		if err != nil {
			return err
		}
	case "yaml", "yml":
		file, err = os.Create(filepath.Join(realPath, "swagger.yml"))
		if err != nil {
			return err
		}
	default:
		return fmt.Errorf("invalid format: %s", s.Format)
	}
	defer file.Close()
	log.Println("creating specification document in", filepath.Join(targetPath, file.Name()))

	var doc spec.Swagger
	info := new(spec.Info)
	doc.Info = info

	doc.Swagger = "2.0"
	doc.Paths = new(spec.Paths)
	doc.Definitions = make(spec.Definitions)

	info.Title = s.Title
	if info.Title == "" {
		info.Title = swag.ToHumanNameTitle(filepath.Base(realPath))
	}
	info.Description = s.Description
	info.Version = s.Version
	info.TermsOfService = s.Terms
	if s.Contact.Name != "" || s.Contact.Email != "" || s.Contact.URL != "" {
		var contact spec.ContactInfo
		contact.Name = s.Contact.Name
		contact.Email = s.Contact.Email
		contact.URL = s.Contact.URL
		info.Contact = &contact
	}
	if s.License.Name != "" || s.License.URL != "" {
		var license spec.License
		license.Name = s.License.Name
		license.URL = s.License.URL
		info.License = &license
	}

	for _, cons := range s.Consumes {
		doc.Consumes = append(doc.Consumes, cons)
	}
	for _, prods := range s.Produces {
		doc.Produces = append(doc.Produces, prods)
	}
	for _, scheme := range s.Schemes {
		doc.Schemes = append(doc.Schemes, scheme)
	}

	if s.Format == "json" {
		enc := json.NewEncoder(file)
		if err := enc.Encode(doc); err != nil {
			return err
		}
		return nil
	}

	b, err := yaml.Marshal(swag.ToDynamicJSON(doc))
	if err != nil {
		return err
	}
	if _, err := file.Write(b); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package cmd_audit

import (
	"flag"
	"github.com/tidwall/gjson"
	"github.com/watermint/toolbox/cmdlet"
	"github.com/watermint/toolbox/dbx_api"
	"github.com/watermint/toolbox/dbx_api/dbx_group"
	"github.com/watermint/toolbox/dbx_api/dbx_member"
	"github.com/watermint/toolbox/dbx_api/dbx_namespace"
	"github.com/watermint/toolbox/dbx_api/dbx_profile"
	"github.com/watermint/toolbox/dbx_api/dbx_sharing"
	"github.com/watermint/toolbox/dbx_api/dbx_team"
	"github.com/watermint/toolbox/report"
	"go.uber.org/zap"
)

type CmdTeamAuditSharing struct {
	*cmdlet.SimpleCommandlet
	groupMembers   map[string][]*dbx_group.GroupMember
	report         report.Factory
	optExpandGroup bool
}

func (CmdTeamAuditSharing) Name() string {
	return "sharing"
}

func (CmdTeamAuditSharing) Desc() string {
	return "Export all sharing information across team"
}

func (z *CmdTeamAuditSharing) FlagConfig(f *flag.FlagSet) {
	z.report.FlagConfig(f)

	descExpandGroup := "Expand group into members"
	f.BoolVar(&z.optExpandGroup, "expand-group", false, descExpandGroup)
}

func (z *CmdTeamAuditSharing) Exec(args []string) {
	apiFile, err := z.ExecContext.LoadOrAuthBusinessFile()
	if err != nil {
		return
	}
	z.report.Init(z.Log())
	defer z.report.Close()

	z.Log().Info("Identify admin user")
	admin, ea, _ := dbx_profile.AuthenticatedAdmin(apiFile)
	if ea.IsFailure() {
		z.DefaultErrorHandler(ea)
		return
	}
	z.Log().Info("Execute scan as admin", zap.String("email", admin.Email))

	z.Log().Info("Scanning Team Info")
	if !z.reportInfo(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Feature")
	if !z.reportFeature(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Members")
	if !z.reportMember(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Group")
	if !z.reportGroup(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Group Member")
	if !z.reportGroupMember(apiFile) {
		return
	}

	z.Log().Info("Scanning Namespace")
	if !z.reportNamespace(apiFile) {
		return
	}

	if z.optExpandGroup {
		z.Log().Info("Preparing for `-expand-group`")
		z.groupMembers = dbx_group.GroupMembers(apiFile, z.Log(), z.DefaultErrorHandler)
		if z.groupMembers == nil {
			z.Log().Warn("Unable to list group members")
			return
		}
	}

	z.Log().Info("Scanning Namespace members")
	if !z.reportNamespaceMember(apiFile, admin) {
		return
	}

	z.Log().Info("Scanning Namespace files")
	if !z.reportNamespaceFile(apiFile, admin) {
		return
	}
}

func (z *CmdTeamAuditSharing) reportInfo(c *dbx_api.Context) bool {
	l := dbx_team.TeamInfoList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(info *dbx_team.TeamInfo) bool {
			z.report.Report(info)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportFeature(c *dbx_api.Context) bool {
	l := dbx_team.FeatureList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(feature *dbx_team.Feature) bool {
			z.report.Report(feature)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportMember(c *dbx_api.Context) bool {
	l := dbx_member.MembersList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(member *dbx_profile.Member) bool {
			z.report.Report(member)
			return true
		},
	}
	return l.List(c, true)
}

func (z *CmdTeamAuditSharing) reportGroup(c *dbx_api.Context) bool {
	gl := dbx_group.GroupList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(group *dbx_group.Group) bool {
			z.report.Report(group)
			return true
		},
	}
	return gl.List(c)
}

func (z *CmdTeamAuditSharing) reportGroupMember(c *dbx_api.Context) bool {
	gl := dbx_group.GroupList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(group *dbx_group.Group) bool {

			gml := dbx_group.GroupMemberList{
				OnError: z.DefaultErrorHandler,
				OnEntry: func(gm *dbx_group.GroupMember) bool {
					z.report.Report(gm)
					return true
				},
			}
			gml.List(c, group)

			return true
		},
	}
	return gl.List(c)
}

func (z *CmdTeamAuditSharing) reportNamespace(c *dbx_api.Context) bool {
	l := dbx_namespace.NamespaceList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(namespace *dbx_namespace.Namespace) bool {
			z.report.Report(namespace)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportNamespaceMember(c *dbx_api.Context, admin *dbx_profile.Profile) bool {
	l := dbx_namespace.NamespaceList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(namespace *dbx_namespace.Namespace) bool {
			if namespace.NamespaceType != "shared_folder" &&
				namespace.NamespaceType != "team_folder" {
				return true
			}

			sl := dbx_sharing.SharedFolderMembers{
				AsAdminId: admin.TeamMemberId,
				OnError:   z.DefaultErrorHandler,
				OnUser: func(user *dbx_sharing.MembershipUser) bool {
					nu := &dbx_namespace.NamespaceUser{
						Namespace: namespace,
						User:      user,
					}
					z.report.Report(nu)
					return true
				},
				OnGroup: func(group *dbx_sharing.MembershipGroup) bool {
					if z.optExpandGroup {
						if gmm, ok := z.groupMembers[group.Group.GroupId]; ok {
							for _, gm := range gmm {
								nu := &dbx_namespace.NamespaceUser{
									Namespace: namespace,
									User: &dbx_sharing.MembershipUser{
										Membership: group.Membership,
										User: &dbx_sharing.User{
											UserAccountId: gm.Profile.AccountId,
											Email:         gm.Profile.Email,
											DisplayName:   gjson.Get(string(gm.Profile.Profile), "name.display_name").String(),
											SameTeam:      true,
											TeamMemberId:  gm.TeamMemberId,
										},
									},
								}
								z.report.Report(nu)
							}
						} else {
							z.Log().Warn(
								"Could not expand group",
								zap.String("group_id", group.Group.GroupId),
								zap.String("group_name", group.Group.GroupName),
							)
							ng := &dbx_namespace.NamespaceGroup{
								Namespace: namespace,
								Group:     group,
							}
							z.report.Report(ng)
						}
					} else {
						ng := &dbx_namespace.NamespaceGroup{
							Namespace: namespace,
							Group:     group,
						}
						z.report.Report(ng)
					}
					return true
				},
				OnInvitee: func(invitee *dbx_sharing.MembershipInvitee) bool {
					ni := &dbx_namespace.NamespaceInvitee{
						Namespace: namespace,
						Invitee:   invitee,
					}
					z.report.Report(ni)
					return true
				},
			}
			sl.List(c, namespace.NamespaceId)
			return true
		},
	}
	return l.List(c)
}

type NamespaceMembershipFileUser struct {
	File *dbx_namespace.NamespaceFile `json:"file"`
	User *dbx_sharing.MembershipUser  `json:"user"`
}
type NamespaceMembershipFileGroup struct {
	File  *dbx_namespace.NamespaceFile `json:"file"`
	Group *dbx_sharing.MembershipGroup `json:"group"`
}
type NamespaceMembershipFileInvitee struct {
	File    *dbx_namespace.NamespaceFile   `json:"file"`
	Invitee *dbx_sharing.MembershipInvitee `json:"invitee"`
}

func (z *CmdTeamAuditSharing) reportNamespaceFile(c *dbx_api.Context, admin *dbx_profile.Profile) bool {
	fileSharing := func(file *dbx_namespace.NamespaceFile) bool {
		lfm := dbx_sharing.SharedFileMembers{
			AsAdminId: admin.TeamMemberId,
			OnError:   z.DefaultErrorHandler,
			OnUser: func(user *dbx_sharing.MembershipUser) bool {
				r := NamespaceMembershipFileUser{
					File: file,
					User: user,
				}
				z.report.Report(r)
				return true
			},
			OnInvitee: func(invitee *dbx_sharing.MembershipInvitee) bool {
				r := NamespaceMembershipFileInvitee{
					File:    file,
					Invitee: invitee,
				}
				z.report.Report(r)
				return true
			},
			OnGroup: func(group *dbx_sharing.MembershipGroup) bool {
				gr := NamespaceMembershipFileGroup{
					File:  file,
					Group: group,
				}

				if !z.optExpandGroup {
					z.report.Report(gr)
					return true
				}

				if gmm, ok := z.groupMembers[group.Group.GroupId]; ok {
					for _, gm := range gmm {
						nu := &NamespaceMembershipFileUser{
							File: file,
							User: &dbx_sharing.MembershipUser{
								Membership: group.Membership,
								User: &dbx_sharing.User{
									UserAccountId: gm.Profile.AccountId,
									Email:         gm.Profile.Email,
									DisplayName:   gjson.Get(string(gm.Profile.Profile), "name.display_name").String(),
									SameTeam:      true,
									TeamMemberId:  gm.TeamMemberId,
								},
							},
						}
						z.report.Report(nu)
					}
				} else {
					z.Log().Warn(
						"Could not expand group",
						zap.String("group_id", group.Group.GroupId),
						zap.String("group_name", group.Group.GroupName),
					)
					z.report.Report(gr)
				}

				return true
			},
		}
		return lfm.List(c, file.File.FileId)
	}

	lns := dbx_namespace.ListNamespaceFile{}
	lns.OptIncludeDeleted = false
	lns.OptIncludeMediaInfo = false
	lns.OptIncludeAppFolder = true
	lns.OptIncludeMemberFolder = true
	lns.OptIncludeSharedFolder = true
	lns.OptIncludeTeamFolder = true
	lns.AsAdminId = admin.TeamMemberId
	lns.OnError = z.DefaultErrorHandler
	lns.OnNamespace = func(namespace *dbx_namespace.Namespace) bool {
		c.Log().Info("Scanning folder",
			zap.String("namespace_type", namespace.NamespaceType),
			zap.String("namespace_id", namespace.NamespaceId),
			zap.String("name", namespace.Name),
		)
		return true
	}
	lns.OnFolder = func(folder *dbx_namespace.NamespaceFolder) bool {
		z.report.Report(folder)
		return true
	}
	lns.OnFile = func(file *dbx_namespace.NamespaceFile) bool {
		z.report.Report(file)

		if file.File.HasExplicitSharedMembers {
			return fileSharing(file)
		}
		return true
	}
	lns.OnDelete = func(deleted *dbx_namespace.NamespaceDeleted) bool {
		z.report.Report(deleted)
		return true
	}
	return lns.List(c)
}
<commit_msg>fix for audit personal spaces<commit_after>package cmd_audit

import (
	"flag"
	"github.com/tidwall/gjson"
	"github.com/watermint/toolbox/cmdlet"
	"github.com/watermint/toolbox/dbx_api"
	"github.com/watermint/toolbox/dbx_api/dbx_group"
	"github.com/watermint/toolbox/dbx_api/dbx_member"
	"github.com/watermint/toolbox/dbx_api/dbx_namespace"
	"github.com/watermint/toolbox/dbx_api/dbx_profile"
	"github.com/watermint/toolbox/dbx_api/dbx_sharing"
	"github.com/watermint/toolbox/dbx_api/dbx_team"
	"github.com/watermint/toolbox/report"
	"go.uber.org/zap"
)

type CmdTeamAuditSharing struct {
	*cmdlet.SimpleCommandlet
	groupMembers   map[string][]*dbx_group.GroupMember
	report         report.Factory
	optExpandGroup bool
}

func (CmdTeamAuditSharing) Name() string {
	return "sharing"
}

func (CmdTeamAuditSharing) Desc() string {
	return "Export all sharing information across team"
}

func (z *CmdTeamAuditSharing) FlagConfig(f *flag.FlagSet) {
	z.report.FlagConfig(f)

	descExpandGroup := "Expand group into members"
	f.BoolVar(&z.optExpandGroup, "expand-group", false, descExpandGroup)
}

func (z *CmdTeamAuditSharing) Exec(args []string) {
	apiFile, err := z.ExecContext.LoadOrAuthBusinessFile()
	if err != nil {
		return
	}
	z.report.Init(z.Log())
	defer z.report.Close()

	z.Log().Info("Identify admin user")
	admin, ea, _ := dbx_profile.AuthenticatedAdmin(apiFile)
	if ea.IsFailure() {
		z.DefaultErrorHandler(ea)
		return
	}
	z.Log().Info("Execute scan as admin", zap.String("email", admin.Email))

	z.Log().Info("Scanning Team Info")
	if !z.reportInfo(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Feature")
	if !z.reportFeature(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Members")
	if !z.reportMember(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Group")
	if !z.reportGroup(apiFile) {
		return
	}

	z.Log().Info("Scanning Team Group Member")
	if !z.reportGroupMember(apiFile) {
		return
	}

	z.Log().Info("Scanning Namespace")
	if !z.reportNamespace(apiFile) {
		return
	}

	if z.optExpandGroup {
		z.Log().Info("Preparing for `-expand-group`")
		z.groupMembers = dbx_group.GroupMembers(apiFile, z.Log(), z.DefaultErrorHandler)
		if z.groupMembers == nil {
			z.Log().Warn("Unable to list group members")
			return
		}
	}

	z.Log().Info("Scanning Namespace members")
	if !z.reportNamespaceMember(apiFile, admin) {
		return
	}

	z.Log().Info("Scanning Namespace files")
	if !z.reportNamespaceFile(apiFile, admin) {
		return
	}
}

func (z *CmdTeamAuditSharing) reportInfo(c *dbx_api.Context) bool {
	l := dbx_team.TeamInfoList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(info *dbx_team.TeamInfo) bool {
			z.report.Report(info)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportFeature(c *dbx_api.Context) bool {
	l := dbx_team.FeatureList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(feature *dbx_team.Feature) bool {
			z.report.Report(feature)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportMember(c *dbx_api.Context) bool {
	l := dbx_member.MembersList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(member *dbx_profile.Member) bool {
			z.report.Report(member)
			return true
		},
	}
	return l.List(c, true)
}

func (z *CmdTeamAuditSharing) reportGroup(c *dbx_api.Context) bool {
	gl := dbx_group.GroupList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(group *dbx_group.Group) bool {
			z.report.Report(group)
			return true
		},
	}
	return gl.List(c)
}

func (z *CmdTeamAuditSharing) reportGroupMember(c *dbx_api.Context) bool {
	gl := dbx_group.GroupList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(group *dbx_group.Group) bool {

			gml := dbx_group.GroupMemberList{
				OnError: z.DefaultErrorHandler,
				OnEntry: func(gm *dbx_group.GroupMember) bool {
					z.report.Report(gm)
					return true
				},
			}
			gml.List(c, group)

			return true
		},
	}
	return gl.List(c)
}

func (z *CmdTeamAuditSharing) reportNamespace(c *dbx_api.Context) bool {
	l := dbx_namespace.NamespaceList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(namespace *dbx_namespace.Namespace) bool {
			z.report.Report(namespace)
			return true
		},
	}
	return l.List(c)
}

func (z *CmdTeamAuditSharing) reportNamespaceMember(c *dbx_api.Context, admin *dbx_profile.Profile) bool {
	l := dbx_namespace.NamespaceList{
		OnError: z.DefaultErrorHandler,
		OnEntry: func(namespace *dbx_namespace.Namespace) bool {
			if namespace.NamespaceType != "shared_folder" &&
				namespace.NamespaceType != "team_folder" {
				return true
			}

			sl := dbx_sharing.SharedFolderMembers{
				AsAdminId: admin.TeamMemberId,
				OnError:   z.DefaultErrorHandler,
				OnUser: func(user *dbx_sharing.MembershipUser) bool {
					nu := &dbx_namespace.NamespaceUser{
						Namespace: namespace,
						User:      user,
					}
					z.report.Report(nu)
					return true
				},
				OnGroup: func(group *dbx_sharing.MembershipGroup) bool {
					if z.optExpandGroup {
						if gmm, ok := z.groupMembers[group.Group.GroupId]; ok {
							for _, gm := range gmm {
								nu := &dbx_namespace.NamespaceUser{
									Namespace: namespace,
									User: &dbx_sharing.MembershipUser{
										Membership: group.Membership,
										User: &dbx_sharing.User{
											UserAccountId: gm.Profile.AccountId,
											Email:         gm.Profile.Email,
											DisplayName:   gjson.Get(string(gm.Profile.Profile), "name.display_name").String(),
											SameTeam:      true,
											TeamMemberId:  gm.TeamMemberId,
										},
									},
								}
								z.report.Report(nu)
							}
						} else {
							z.Log().Warn(
								"Could not expand group",
								zap.String("group_id", group.Group.GroupId),
								zap.String("group_name", group.Group.GroupName),
							)
							ng := &dbx_namespace.NamespaceGroup{
								Namespace: namespace,
								Group:     group,
							}
							z.report.Report(ng)
						}
					} else {
						ng := &dbx_namespace.NamespaceGroup{
							Namespace: namespace,
							Group:     group,
						}
						z.report.Report(ng)
					}
					return true
				},
				OnInvitee: func(invitee *dbx_sharing.MembershipInvitee) bool {
					ni := &dbx_namespace.NamespaceInvitee{
						Namespace: namespace,
						Invitee:   invitee,
					}
					z.report.Report(ni)
					return true
				},
			}
			sl.List(c, namespace.NamespaceId)
			return true
		},
	}
	return l.List(c)
}

type NamespaceMembershipFileUser struct {
	File *dbx_namespace.NamespaceFile `json:"file"`
	User *dbx_sharing.MembershipUser  `json:"user"`
}
type NamespaceMembershipFileGroup struct {
	File  *dbx_namespace.NamespaceFile `json:"file"`
	Group *dbx_sharing.MembershipGroup `json:"group"`
}
type NamespaceMembershipFileInvitee struct {
	File    *dbx_namespace.NamespaceFile   `json:"file"`
	Invitee *dbx_sharing.MembershipInvitee `json:"invitee"`
}

func (z *CmdTeamAuditSharing) reportNamespaceFile(c *dbx_api.Context, admin *dbx_profile.Profile) bool {
	fileSharing := func(file *dbx_namespace.NamespaceFile) bool {
		lfm := dbx_sharing.SharedFileMembers{
			OnError:   z.DefaultErrorHandler,
			OnUser: func(user *dbx_sharing.MembershipUser) bool {
				r := NamespaceMembershipFileUser{
					File: file,
					User: user,
				}
				z.report.Report(r)
				return true
			},
			OnInvitee: func(invitee *dbx_sharing.MembershipInvitee) bool {
				r := NamespaceMembershipFileInvitee{
					File:    file,
					Invitee: invitee,
				}
				z.report.Report(r)
				return true
			},
			OnGroup: func(group *dbx_sharing.MembershipGroup) bool {
				gr := NamespaceMembershipFileGroup{
					File:  file,
					Group: group,
				}

				if !z.optExpandGroup {
					z.report.Report(gr)
					return true
				}

				if gmm, ok := z.groupMembers[group.Group.GroupId]; ok {
					for _, gm := range gmm {
						nu := &NamespaceMembershipFileUser{
							File: file,
							User: &dbx_sharing.MembershipUser{
								Membership: group.Membership,
								User: &dbx_sharing.User{
									UserAccountId: gm.Profile.AccountId,
									Email:         gm.Profile.Email,
									DisplayName:   gjson.Get(string(gm.Profile.Profile), "name.display_name").String(),
									SameTeam:      true,
									TeamMemberId:  gm.TeamMemberId,
								},
							},
						}
						z.report.Report(nu)
					}
				} else {
					z.Log().Warn(
						"Could not expand group",
						zap.String("group_id", group.Group.GroupId),
						zap.String("group_name", group.Group.GroupName),
					)
					z.report.Report(gr)
				}

				return true
			},
		}

		if file.Namespace.TeamMemberId != "" {
			lfm.AsMemberId = file.Namespace.TeamMemberId
		} else {
			lfm.AsAdminId = admin.TeamMemberId
		}

		return lfm.List(c, file.File.FileId)
	}

	lns := dbx_namespace.ListNamespaceFile{}
	lns.OptIncludeDeleted = false
	lns.OptIncludeMediaInfo = false
	lns.OptIncludeAppFolder = true
	lns.OptIncludeMemberFolder = true
	lns.OptIncludeSharedFolder = true
	lns.OptIncludeTeamFolder = true
	lns.AsAdminId = admin.TeamMemberId
	lns.OnError = z.DefaultErrorHandler
	lns.OnNamespace = func(namespace *dbx_namespace.Namespace) bool {
		z.Log().Info("Scanning folder",
			zap.String("namespace_type", namespace.NamespaceType),
			zap.String("namespace_id", namespace.NamespaceId),
			zap.String("name", namespace.Name),
		)
		return true
	}
	lns.OnFolder = func(folder *dbx_namespace.NamespaceFolder) bool {
		z.report.Report(folder)
		return true
	}
	lns.OnFile = func(file *dbx_namespace.NamespaceFile) bool {
		z.report.Report(file)

		if file.File.HasExplicitSharedMembers {
			return fileSharing(file)
		}
		return true
	}
	lns.OnDelete = func(deleted *dbx_namespace.NamespaceDeleted) bool {
		z.report.Report(deleted)
		return true
	}
	return lns.List(c)
}
<|endoftext|>