github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/action.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Copyright 2016 Cloudbase Solutions
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package common
     6  
     7  import (
     8  	"github.com/juju/errors"
     9  	"github.com/juju/names/v5"
    10  
    11  	apiservererrors "github.com/juju/juju/apiserver/errors"
    12  	"github.com/juju/juju/apiserver/facade"
    13  	"github.com/juju/juju/rpc/params"
    14  	"github.com/juju/juju/state"
    15  	"github.com/juju/juju/state/watcher"
    16  )
    17  
    18  // ParamsActionExecutionResultsToStateActionResults does exactly what
    19  // the name implies.
    20  func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) {
    21  	var status state.ActionStatus
    22  	switch arg.Status {
    23  	case params.ActionCancelled:
    24  		status = state.ActionCancelled
    25  	case params.ActionCompleted:
    26  		status = state.ActionCompleted
    27  	case params.ActionFailed:
    28  		status = state.ActionFailed
    29  	case params.ActionPending:
    30  		status = state.ActionPending
    31  	case params.ActionAborting:
    32  		status = state.ActionAborting
    33  	case params.ActionAborted:
    34  		status = state.ActionAborted
    35  	case params.ActionError:
    36  		status = state.ActionError
    37  	default:
    38  		return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status)
    39  	}
    40  	return state.ActionResults{
    41  		Status:  status,
    42  		Results: arg.Results,
    43  		Message: arg.Message,
    44  	}, nil
    45  }
    46  
    47  // TagToActionReceiverFn takes a tag string and tries to convert it to an
    48  // ActionReceiver. It needs a findEntity function passed in that can search for the tags in state.
    49  func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) {
    50  	return func(tag string) (state.ActionReceiver, error) {
    51  		receiverTag, err := names.ParseTag(tag)
    52  		if err != nil {
    53  			return nil, errors.NotValidf("%q", tag)
    54  		}
    55  		entity, err := findEntity(receiverTag)
    56  		if err != nil {
    57  			return nil, errors.NotFoundf("%q", receiverTag)
    58  		}
    59  		receiver, ok := entity.(state.ActionReceiver)
    60  		if !ok {
    61  			return nil, errors.NotImplementedf("action receiver interface on entity %s", tag)
    62  		}
    63  		return receiver, nil
    64  	}
    65  }
    66  
    67  // AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state
    68  // and returns a function that can fetch an action from state by id and check the authorization.
    69  func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) {
    70  	return func(tag string) (state.Action, error) {
    71  		actionTag, err := names.ParseActionTag(tag)
    72  		if err != nil {
    73  			return nil, errors.Trace(err)
    74  		}
    75  		action, err := getActionByTag(actionTag)
    76  		if err != nil {
    77  			return nil, errors.Trace(err)
    78  		}
    79  		receiverTag, err := names.ActionReceiverTag(action.Receiver())
    80  		if err != nil {
    81  			return nil, errors.Trace(err)
    82  		}
    83  		if !canAccess(receiverTag) {
    84  			return nil, apiservererrors.ErrPerm
    85  		}
    86  		return action, nil
    87  	}
    88  }
    89  
    90  // BeginActions calls begin on every action passed in through args.
    91  // It's a helper function currently used by the uniter and by machineactions
    92  // It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn
    93  func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults {
    94  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))}
    95  
    96  	for i, arg := range args.Entities {
    97  		action, err := actionFn(arg.Tag)
    98  		if err != nil {
    99  			results.Results[i].Error = apiservererrors.ServerError(err)
   100  			continue
   101  		}
   102  
   103  		_, err = action.Begin()
   104  		if err != nil {
   105  			results.Results[i].Error = apiservererrors.ServerError(err)
   106  			continue
   107  		}
   108  	}
   109  
   110  	return results
   111  }
   112  
   113  // FinishActions saves the result of a completed Action.
   114  // It's a helper function currently used by the uniter and by machineactions
   115  // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
   116  func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults {
   117  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))}
   118  
   119  	for i, arg := range args.Results {
   120  		action, err := actionFn(arg.ActionTag)
   121  		if err != nil {
   122  			results.Results[i].Error = apiservererrors.ServerError(err)
   123  			continue
   124  		}
   125  		actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg)
   126  		if err != nil {
   127  			results.Results[i].Error = apiservererrors.ServerError(err)
   128  			continue
   129  		}
   130  
   131  		_, err = action.Finish(actionResults)
   132  		if err != nil {
   133  			results.Results[i].Error = apiservererrors.ServerError(err)
   134  			continue
   135  		}
   136  	}
   137  
   138  	return results
   139  }
   140  
   141  // Actions returns the Actions by Tags passed in and ensures that the receiver asking for
   142  // them is the same one that has the action.
   143  // It's a helper function currently used by the uniter and by machineactions.
   144  // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
   145  func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults {
   146  	results := params.ActionResults{
   147  		Results: make([]params.ActionResult, len(args.Entities)),
   148  	}
   149  
   150  	for i, arg := range args.Entities {
   151  		action, err := actionFn(arg.Tag)
   152  		if err != nil {
   153  			results.Results[i].Error = apiservererrors.ServerError(err)
   154  			continue
   155  		}
   156  		if action.Status() != state.ActionPending {
   157  			results.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrActionNotAvailable)
   158  			continue
   159  		}
   160  		parallel := action.Parallel()
   161  		executionGroup := action.ExecutionGroup()
   162  		results.Results[i].Action = &params.Action{
   163  			Name:           action.Name(),
   164  			Parameters:     action.Parameters(),
   165  			Parallel:       &parallel,
   166  			ExecutionGroup: &executionGroup,
   167  		}
   168  	}
   169  
   170  	return results
   171  }
   172  
   173  // WatchOneActionReceiverNotifications returns a function for creating a
   174  // watcher on all action notifications (action adds + changes) for one receiver.
   175  // It needs a tagToActionReceiver function and a registerFunc to register
   176  // resources.
   177  // It's a helper function currently used by the uniter and by machineactions
   178  func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r facade.Resource) string) func(names.Tag) (params.StringsWatchResult, error) {
   179  	return func(tag names.Tag) (params.StringsWatchResult, error) {
   180  		nothing := params.StringsWatchResult{}
   181  		receiver, err := tagToActionReceiver(tag.String())
   182  		if err != nil {
   183  			return nothing, err
   184  		}
   185  		watch := receiver.WatchActionNotifications()
   186  
   187  		if changes, ok := <-watch.Changes(); ok {
   188  			return params.StringsWatchResult{
   189  				StringsWatcherId: registerFunc(watch),
   190  				Changes:          changes,
   191  			}, nil
   192  		}
   193  		return nothing, watcher.EnsureErr(watch)
   194  	}
   195  }
   196  
   197  // WatchPendingActionsForReceiver returns a function for creating a
   198  // watcher on new pending Actions for one receiver.
   199  // It needs a tagToActionReceiver function and a registerFunc to register
   200  // resources.
   201  // It's a helper function currently used by the uniter and by machineactions
   202  func WatchPendingActionsForReceiver(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r facade.Resource) string) func(names.Tag) (params.StringsWatchResult, error) {
   203  	return func(tag names.Tag) (params.StringsWatchResult, error) {
   204  		nothing := params.StringsWatchResult{}
   205  		receiver, err := tagToActionReceiver(tag.String())
   206  		if err != nil {
   207  			return nothing, err
   208  		}
   209  		watch := receiver.WatchPendingActionNotifications()
   210  
   211  		if changes, ok := <-watch.Changes(); ok {
   212  			return params.StringsWatchResult{
   213  				StringsWatcherId: registerFunc(watch),
   214  				Changes:          changes,
   215  			}, nil
   216  		}
   217  		return nothing, watcher.EnsureErr(watch)
   218  	}
   219  }
   220  
   221  // WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver.
   222  // It's a helper function currently used by the uniter and by machineactions
   223  // canAccess is passed in by the respective caller to provide authorization.
   224  // watchOne is usually a function created by WatchOneActionReceiverNotifications
   225  func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults {
   226  	result := params.StringsWatchResults{
   227  		Results: make([]params.StringsWatchResult, len(args.Entities)),
   228  	}
   229  
   230  	for i, entity := range args.Entities {
   231  		tag, err := names.ActionReceiverFromTag(entity.Tag)
   232  		if err != nil {
   233  			result.Results[i].Error = apiservererrors.ServerError(err)
   234  			continue
   235  		}
   236  		err = apiservererrors.ErrPerm
   237  		if canAccess(tag) {
   238  			result.Results[i], err = watchOne(tag)
   239  		}
   240  		result.Results[i].Error = apiservererrors.ServerError(err)
   241  	}
   242  
   243  	return result
   244  }
   245  
   246  // GetActionsFn declares the function type that returns a slice of
   247  // state.Action and error, used to curry specific list functions.
   248  type GetActionsFn func() ([]state.Action, error)
   249  
   250  // ConvertActions takes a generic getActionsFn to obtain a slice
   251  // of state.Action and then converts them to the API slice of
   252  // params.ActionResult.
   253  func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) {
   254  	items := []params.ActionResult{}
   255  	actions, err := fn()
   256  	if err != nil {
   257  		return items, err
   258  	}
   259  	for _, action := range actions {
   260  		if action == nil {
   261  			continue
   262  		}
   263  		items = append(items, MakeActionResult(ar.Tag(), action))
   264  	}
   265  	return items, nil
   266  }
   267  
   268  // MakeActionResult does the actual type conversion from state.Action
   269  // to params.ActionResult.
   270  func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult {
   271  	output, message := action.Results()
   272  	parallel := action.Parallel()
   273  	executionGroup := action.ExecutionGroup()
   274  	result := params.ActionResult{
   275  		Action: &params.Action{
   276  			Receiver:       actionReceiverTag.String(),
   277  			Tag:            action.ActionTag().String(),
   278  			Name:           action.Name(),
   279  			Parameters:     action.Parameters(),
   280  			Parallel:       &parallel,
   281  			ExecutionGroup: &executionGroup,
   282  		},
   283  		Status:    string(action.Status()),
   284  		Message:   message,
   285  		Output:    output,
   286  		Enqueued:  action.Enqueued(),
   287  		Started:   action.Started(),
   288  		Completed: action.Completed(),
   289  	}
   290  	for _, m := range action.Messages() {
   291  		result.Log = append(result.Log, params.ActionMessage{
   292  			Timestamp: m.Timestamp(),
   293  			Message:   m.Message(),
   294  		})
   295  	}
   296  
   297  	return result
   298  }