Current section

Files

Jump to
erlcloud src erlcloud_workspaces.erl
Raw

src/erlcloud_workspaces.erl

%% @doc
%% An Erlang interface to AWS Workspaces.
%%
%% Output is in the form of `{ok, Value}' or `{error, Reason}'. The
%% format of `Value' is controlled by the `out' option, which defaults
%% to `json'. The possible values are:
%%
%% * `json' - The output from Workspaces as processed by `jsx:decode'
%% but with no further manipulation.
%%
%% * `record' - A record containing all the information from the
%% Workspaces response except field types.
%%
%% Workspaces errors are returned in the form `{error, {ErrorCode, Message}}'
%% where `ErrorCode' and 'Message' are both binary
%% strings.
%% See the unit tests for additional usage examples beyond what are
%% provided for each function.
%%
%% @end
-module(erlcloud_workspaces).
-include("erlcloud_aws.hrl").
-include("erlcloud_workspaces.hrl").
%%% Library initialization.
-export([configure/2, configure/3, configure/4, new/2, new/3, new/4]).
-define(API_VERSION, "20150408").
-define(OUTPUT_CHOICES, [json, record, map]).
-export([
describe_tags/1, describe_tags/2,
describe_workspaces/0, describe_workspaces/1, describe_workspaces/2,
describe_workspace_directories/0, describe_workspace_directories/1, describe_workspace_directories/2
]).
-export_type([
describe_workspaces_opt/0,
describe_workspaces_opts/0,
describe_workspace_directories_opt/0,
describe_workspace_directories_opts/0
]).
%%%------------------------------------------------------------------------------
%%% Library initialization.
%%%------------------------------------------------------------------------------
-spec new(string(), string()) -> aws_config().
new(AccessKeyID, SecretAccessKey) ->
#aws_config{
access_key_id=AccessKeyID,
secret_access_key=SecretAccessKey
}.
-spec new(string(), string(), string()) -> aws_config().
new(AccessKeyID, SecretAccessKey, Host) ->
#aws_config{
access_key_id=AccessKeyID,
secret_access_key=SecretAccessKey,
workspaces_host=Host
}.
-spec new(string(), string(), string(), non_neg_integer()) -> aws_config().
new(AccessKeyID, SecretAccessKey, Host, Port) ->
#aws_config{
access_key_id=AccessKeyID,
secret_access_key=SecretAccessKey,
workspaces_host=Host,
workspaces_port=Port
}.
-spec configure(string(), string()) -> ok.
configure(AccessKeyID, SecretAccessKey) ->
put(aws_config, new(AccessKeyID, SecretAccessKey)),
ok.
-spec configure(string(), string(), string()) -> ok.
configure(AccessKeyID, SecretAccessKey, Host) ->
put(aws_config, new(AccessKeyID, SecretAccessKey, Host)),
ok.
-spec configure(string(), string(), string(), non_neg_integer()) -> ok.
configure(AccessKeyID, SecretAccessKey, Host, Port) ->
put(aws_config, new(AccessKeyID, SecretAccessKey, Host, Port)),
ok.
default_config() ->
erlcloud_aws:default_config().
%%%------------------------------------------------------------------------------
%%% Shared types
%%%------------------------------------------------------------------------------
-type string_param() :: binary() | string().
-type json_pair() :: {binary() | atom(), jsx:json_term()}.
-type json_return() :: {ok, jsx:json_term()} | {error, term()}.
-type workspaces_return(Record) :: {ok, jsx:json_term() | Record } | {error, term()}.
-type decode_fun() :: fun((jsx:json_term(), decode_opts()) -> tuple()).
%%%------------------------------------------------------------------------------
%%% Shared Options
%%%------------------------------------------------------------------------------
-type out_type() :: json | record.
-type out_opt() :: {out, out_type()}.
-type property() :: proplists:property().
-type aws_opts() :: [json_pair()].
-type workspaces_opts() :: [out_opt()].
-type opts() :: {aws_opts(), workspaces_opts()}.
-spec verify_workspaces_opt(atom(), term()) -> ok.
verify_workspaces_opt(out, Value) ->
case lists:member(Value, ?OUTPUT_CHOICES) of
true ->
ok;
false ->
error({erlcloud_workspaces, {invalid_opt, {out, Value}}})
end;
verify_workspaces_opt(Name, Value) ->
error({erlcloud_workspaces, {invalid_opt, {Name, Value}}}).
-type opt_table_entry() :: {atom(), binary(), fun((_) -> jsx:json_term())}.
-type opt_table() :: [opt_table_entry()].
-spec opt_folder(opt_table(), property(), opts()) -> opts().
opt_folder(_, {_, undefined}, Opts) ->
%% ignore options set to undefined
Opts;
opt_folder(Table, {Name, Value}, {AwsOpts, EcsOpts}) ->
case lists:keyfind(Name, 1, Table) of
{Name, Key, ValueFun} ->
{[{Key, ValueFun(Value)} | AwsOpts], EcsOpts};
false ->
verify_workspaces_opt(Name, Value),
{AwsOpts, [{Name, Value} | EcsOpts]}
end.
-spec opts(opt_table(), proplist()) -> opts().
opts(Table, Opts) when is_list(Opts) ->
%% remove duplicate options
Opts1 = lists:ukeysort(1, proplists:unfold(Opts)),
lists:foldl(fun(Opt, A) -> opt_folder(Table, Opt, A) end, {[], []}, Opts1);
opts(_, _) ->
error({erlcloud_workspaces, opts_not_list}).
%%%------------------------------------------------------------------------------
%%% Shared Decoders
%%%------------------------------------------------------------------------------
-type decode_opt() :: {typed, boolean()}.
-type decode_opts() :: [decode_opt()].
-type record_desc() :: {tuple(), field_table()}.
-spec id(X) -> X.
id(X) -> X.
-spec id(X, decode_opts()) -> X.
id(X, _) -> X.
-type field_table() :: [{binary(), pos_integer(),
fun((jsx:json_term(), decode_opts()) -> term())}].
-spec decode_folder(field_table(), json_pair(), decode_opts(), tuple()) -> tuple().
decode_folder(Table, {Key, Value}, Opts, A) ->
case lists:keyfind(Key, 1, Table) of
{Key, Index, ValueFun} ->
setelement(Index, A, ValueFun(Value, Opts));
false ->
A
end.
-spec decode_record(record_desc(), jsx:json_term(), decode_opts()) -> tuple().
decode_record({Record, _}, [{}], _) ->
%% jsx returns [{}] for empty objects
Record;
decode_record({Record, Table}, Json, Opts) ->
lists:foldl(fun(Pair, A) -> decode_folder(Table, Pair, Opts, A) end, Record, Json).
%%%------------------------------------------------------------------------------
%%% Output
%%%------------------------------------------------------------------------------
-spec out(json_return(), decode_fun(), workspaces_opts())
-> {ok, jsx:json_term() | tuple()} |
{simple, term()} |
{error, term()}.
out({error, Reason}, _, _) ->
{error, Reason};
out({ok, Json}, Decode, Opts) ->
case proplists:get_value(out, Opts, record) of
json ->
{ok, Json};
record ->
{ok, Decode(Json, [])};
map ->
{ok, erlcloud_util:proplists_to_map(Json)}
end.
%%%------------------------------------------------------------------------------
%%% Shared Records
%%%------------------------------------------------------------------------------
-spec workspace_record() -> record_desc().
workspace_record() ->
{#workspace{},
[
{<<"BundleId">>, #workspace.bundle_id, fun id/2},
{<<"ComputerName">>, #workspace.computer_name, fun id/2},
{<<"DirectoryId">>, #workspace.directory_id, fun id/2},
{<<"ErrorCode">>, #workspace.error_code, fun id/2},
{<<"ErrorMessage">>, #workspace.error_message, fun id/2},
{<<"IpAddress">>, #workspace.ip_address, fun id/2},
{<<"ModificationStates">>, #workspace.modification_states, fun decode_modification_state_list/2},
{<<"RootVolumeEncryptionEnabled">>, #workspace.root_volume_encryption_enabled, fun id/2},
{<<"State">>, #workspace.state, fun id/2},
{<<"SubnetId">>, #workspace.subnet_id, fun id/2},
{<<"UserName">>, #workspace.user_name, fun id/2},
{<<"UserVolumeEncryptionEnabled">>, #workspace.user_volume_encryption_enabled, fun id/2},
{<<"VolumeEncryptionKey">>, #workspace.volume_encryption_key, fun id/2},
{<<"WorkspaceId">>, #workspace.workspace_id, fun id/2},
{<<"WorkspaceProperties">>, #workspace.workspace_properties, fun decode_workspace_properties/2}
]
}.
-spec modification_state_record() -> record_desc().
modification_state_record() ->
{#workspace_modification_state{},
[
{<<"Resource">>, #workspace_modification_state.resource, fun id/2},
{<<"State">>, #workspace_modification_state.state, fun id/2}
]
}.
-spec workspace_properties_record() -> record_desc().
workspace_properties_record() ->
{#workspace_properties{},
[
{<<"ComputeTypeName">>, #workspace_properties.computer_type_name, fun id/2},
{<<"RootVolumeSizeGib">>, #workspace_properties.root_volume_size_gib, fun id/2},
{<<"RunningMode">>, #workspace_properties.running_mode, fun id/2},
{<<"RunningModeAutoStopTimeoutInMinutes">>, #workspace_properties.running_mode_auto_stop_timeout_in_minutes, fun id/2},
{<<"UserVolumeSizeGib">>, #workspace_properties.user_volume_size_gib, fun id/2}
]
}.
-spec tag_record() -> record_desc().
tag_record() ->
{#workspaces_tag{},
[
{<<"Key">>, #workspaces_tag.key, fun id/2},
{<<"Value">>, #workspaces_tag.value, fun id/2}
]
}.
-spec workspace_directory_record() -> record_desc().
workspace_directory_record() ->
{#workspace_directory{},
[
{<<"Alias">>, #workspace_directory.alias, fun id/2},
{<<"CustomerUserName">>, #workspace_directory.customer_user_name, fun id/2},
{<<"DirectoryId">>, #workspace_directory.directory_id, fun id/2},
{<<"DirectoryName">>, #workspace_directory.directory_name, fun id/2},
{<<"DirectoryType">>, #workspace_directory.directory_type, fun id/2},
{<<"DnsIpAddresses">>, #workspace_directory.dns_ip_address, fun id/2},
{<<"IamRoleId">>, #workspace_directory.iam_role_id, fun id/2},
{<<"ipGroupIds">>, #workspace_directory.ip_group_ids, fun id/2},
{<<"RegistrationCode">>, #workspace_directory.registration_code, fun id/2},
{<<"SelfservicePermissions">>, #workspace_directory.selfservice_permissions, fun decode_selfservice_permissions/2},
{<<"State">>, #workspace_directory.state, fun id/2},
{<<"SubnetIds">>, #workspace_directory.subnet_ids, fun id/2},
{<<"Tenancy">>, #workspace_directory.tenancy, fun id/2},
{<<"WorkspaceAccessProperties">>, #workspace_directory.workspace_access_properties, fun decode_workspace_access_properties/2},
{<<"WorkspaceCreationProperties">>, #workspace_directory.workspace_creation_properties, fun decode_workspace_creation_properties/2},
{<<"WorkspaceSecurityGroupId">>, #workspace_directory.workspace_security_group_id, fun id/2}
]
}.
-spec workspaces_selfservice_permissions_record() -> record_desc().
workspaces_selfservice_permissions_record() ->
{#workspaces_selfservice_permissions{},
[
{<<"ChangeComputeType">>, #workspaces_selfservice_permissions.change_compute_type, fun id/2},
{<<"IncreaseVolumeSize">>, #workspaces_selfservice_permissions.increase_volume_size, fun id/2},
{<<"RebuildWorkspace">>, #workspaces_selfservice_permissions.rebuild_workspace, fun id/2},
{<<"RestartWorkspace">>, #workspaces_selfservice_permissions.restart_workspace, fun id/2},
{<<"SwitchRunningMode">>, #workspaces_selfservice_permissions.switch_running_mode, fun id/2}
]
}.
-spec workspace_access_properties_record() -> record_desc().
workspace_access_properties_record() ->
{#workspace_access_properties{},
[
{<<"DeviceTypeAndroid">>, #workspace_access_properties.device_type_android, fun id/2},
{<<"DeviceTypeChromeOs">>, #workspace_access_properties.device_type_chrome_os, fun id/2},
{<<"DeviceTypeIos">>, #workspace_access_properties.device_type_ios, fun id/2},
{<<"DeviceTypeOsx">>, #workspace_access_properties.device_type_osx, fun id/2},
{<<"DeviceTypeWeb">>, #workspace_access_properties.device_type_web, fun id/2},
{<<"DeviceTypeWindows">>, #workspace_access_properties.device_type_windows, fun id/2},
{<<"DeviceTypeZeroClient">>, #workspace_access_properties.device_type_zero_client, fun id/2}
]
}.
-spec workspace_creation_properties_record() -> record_desc().
workspace_creation_properties_record() ->
{#workspace_creation_properties{},
[
{<<"CustomSecurityGroupId">>, #workspace_creation_properties.custom_security_group_id, fun id/2},
{<<"DefaultOu">>, #workspace_creation_properties.default_ou, fun id/2},
{<<"EnableInternetAccess">>, #workspace_creation_properties.enable_internet_access, fun id/2},
{<<"EnableMaintenanceMode">>, #workspace_creation_properties.enable_maintenance_mode, fun id/2},
{<<"EnableWorkDocs">>, #workspace_creation_properties.enable_work_docs, fun id/2},
{<<"UserEnabledAsLocalAdministrator">>, #workspace_creation_properties.user_enabled_as_local_administrator, fun id/2}
]
}.
decode_tags_list(V, Opts) ->
[decode_record(tag_record(), I, Opts) || I <- V].
decode_workspace_properties(V, Opts) ->
decode_record(workspace_properties_record(), V, Opts).
decode_modification_state_list(V, Opts) ->
[decode_record(modification_state_record(), I, Opts) || I <- V].
decode_workspaces_list(V, Opts) ->
[decode_record(workspace_record(), I, Opts) || I <- V].
decode_workspace_directories_list(V, Opts) ->
[decode_record(workspace_directory_record(), I, Opts) || I <- V].
decode_selfservice_permissions(V, Opts) ->
decode_record(workspaces_selfservice_permissions_record(), V, Opts).
decode_workspace_access_properties(V, Opts) ->
decode_record(workspace_access_properties_record(), V, Opts).
decode_workspace_creation_properties(V, Opts) ->
decode_record(workspace_creation_properties_record(), V, Opts).
%%%------------------------------------------------------------------------------
%%% AWS Workspaces API Functions
%%%------------------------------------------------------------------------------
%%%------------------------------------------------------------------------------
%% DescribeTags
%%%------------------------------------------------------------------------------
-type describe_tags_opt() :: {resource_id, string_param()} |
out_opt().
-type describe_tags_opts() :: [describe_tags_opt()].
-spec describe_tags_opts() -> opt_table().
describe_tags_opts() ->
[
{resource_id, <<"ResourceId">>, fun encode_json_value/1}
].
-spec describe_tags(Opts :: describe_tags_opts()) -> workspaces_return([#workspaces_tag{}]).
describe_tags(Opts) ->
describe_tags(Opts, default_config()).
%%%------------------------------------------------------------------------------
%% @doc
%% Workspaces API
%% [https://docs.aws.amazon.com/workspaces/latest/api/API_DescribeTags.html]
%%
%% ===Example===
%%
%% Describe tags for workspace id "ws-c8wvb67py"
%%
%% `
%% {ok, Tags} = erlcloud_workspaces:describe_tags([{resource_id, "ws-c8wvb67py"}, {out, json}])
%% '
%% @end
%%%------------------------------------------------------------------------------
-spec describe_tags(Opts :: describe_tags_opts(), Config :: aws_config()) -> workspaces_return([#workspaces_tag{}]).
describe_tags(Opts, #aws_config{} = Config) ->
{AwsOpts, WorkspacesOpts} = opts(describe_tags_opts(), Opts),
Return = workspaces_request(
Config,
"DescribeTags",
AwsOpts),
out(Return, fun(Json, UOpts) ->
TagsList = proplists:get_value(<<"TagList">>, Json),
decode_tags_list(TagsList, UOpts)
end,
WorkspacesOpts).
%%%------------------------------------------------------------------------------
%% DescribeWorkspaces
%%%------------------------------------------------------------------------------
-type describe_workspaces_opt() :: {bundle_id, string_param()} |
{directory_id, string_param()} |
{limit, pos_integer()} |
{next_token, string_param()} |
{user_name, string_param()} |
{workspace_ids, [string_param()]} |
out_opt().
-type describe_workspaces_opts() :: [describe_workspaces_opt()].
-spec describe_workspaces_opts() -> opt_table().
describe_workspaces_opts() ->
[
{bundle_id, <<"BundleId">>, fun encode_json_value/1},
{directory_id, <<"DirectoryId">>, fun encode_json_value/1},
{limit, <<"Limit">>, fun id/1},
{next_token, <<"NextToken">>, fun encode_json_value/1},
{user_name, <<"UserName">>, fun encode_json_value/1},
{workspace_ids, <<"WorkspaceIds">>, fun encode_json_value/1}
].
-spec describe_workspaces_record() -> record_desc().
describe_workspaces_record() ->
{#describe_workspaces{},
[{<<"NextToken">>, #describe_workspaces.next_token, fun id/2},
{<<"Workspaces">>, #describe_workspaces.workspaces, fun decode_workspaces_list/2}
]}.
-spec describe_workspaces() -> workspaces_return(#describe_workspaces{}).
describe_workspaces() ->
describe_workspaces([], default_config()).
-spec describe_workspaces(describe_workspaces_opts() | aws_config()) -> workspaces_return(#describe_workspaces{}).
describe_workspaces(#aws_config{} = Config) ->
describe_workspaces([], Config);
describe_workspaces(Opts) ->
describe_workspaces(Opts, default_config()).
%%%------------------------------------------------------------------------------
%% @doc
%% Workspaces API
%% [https://docs.aws.amazon.com/workspaces/latest/api/API_DescribeWorkspaces.html]
%%
%% ===Example===
%%
%% Describe workspaces in Directory "TestDirectory"
%%
%% `
%% {ok, Clusters} = erlcloud_workspaces:describe_workspaces([{directory_id, "TestDirectory"}, {out, json}])
%% '
%% @end
%%%------------------------------------------------------------------------------
-spec describe_workspaces(Opts :: describe_workspaces_opts(), Config :: aws_config()) -> workspaces_return(#describe_workspaces{}).
describe_workspaces(Opts, #aws_config{} = Config) ->
{AwsOpts, WorkspacesOpts} = opts(describe_workspaces_opts(), Opts),
Return = workspaces_request(
Config,
"DescribeWorkspaces",
AwsOpts),
out(Return, fun(Json, UOpts) -> decode_record(describe_workspaces_record(), Json, UOpts) end,
WorkspacesOpts).
%%%------------------------------------------------------------------------------
%% DescribeWorkspaceDirectories
%%%------------------------------------------------------------------------------
-type describe_workspace_directories_opt() :: {directory_ids, [string_param()]} |
{limit, pos_integer()} |
{next_token, string_param()} |
out_opt().
-type describe_workspace_directories_opts() :: [describe_workspace_directories_opt()].
-spec describe_workspace_directories_opts() -> opt_table().
describe_workspace_directories_opts() ->
[
{directory_ids, <<"DirectoryIds">>, fun encode_json_value/1},
{limit, <<"Limit">>, fun id/1},
{next_token, <<"NextToken">>, fun encode_json_value/1}
].
-spec describe_workspace_directories_record() -> record_desc().
describe_workspace_directories_record() ->
{#describe_workspace_directories{},
[{<<"NextToken">>, #describe_workspace_directories.next_token, fun id/2},
{<<"Directories">>, #describe_workspace_directories.workspace_directories, fun decode_workspace_directories_list/2}
]}.
-spec describe_workspace_directories() -> workspaces_return(#describe_workspace_directories{}).
describe_workspace_directories() ->
describe_workspace_directories([], default_config()).
-spec describe_workspace_directories(describe_workspace_directories_opts() | aws_config())
-> workspaces_return(#describe_workspace_directories{}).
describe_workspace_directories(#aws_config{} = Config) ->
describe_workspace_directories([], Config);
describe_workspace_directories(Opts) ->
describe_workspace_directories(Opts, default_config()).
%%%------------------------------------------------------------------------------
%% @doc
%% Workspaces API
%% [https://docs.aws.amazon.com/workspaces/latest/api/API_DescribeWorkspaceDirectories.html]
%%
%% ===Example===
%%
%% Describe workspaces directory "TestDirectory"
%%
%% `
%% {ok, Clusters} = erlcloud_workspaces:describe_workspace_directories([{directory_ids, ["TestDirectory"]}, {out, json}])
%% '
%% @end
%%%------------------------------------------------------------------------------
-spec describe_workspace_directories(
Opts :: describe_workspace_directories_opts(),
Config :: aws_config())
-> workspaces_return(#describe_workspace_directories{}).
describe_workspace_directories(Opts, #aws_config{} = Config) ->
{AwsOpts, WorkspacesOpts} = opts(describe_workspace_directories_opts(), Opts),
Return = workspaces_request(
Config,
"DescribeWorkspaceDirectories",
AwsOpts),
out(Return, fun(Json, UOpts) -> decode_record(describe_workspace_directories_record(), Json, UOpts) end,
WorkspacesOpts).
%%%------------------------------------------------------------------------------
%%% Internal Functions
%%%------------------------------------------------------------------------------
workspaces_request(Config, Operation, Body) ->
case erlcloud_aws:update_config(Config) of
{ok, Config1} ->
workspaces_request_no_update(Config1, Operation, Body);
{error, Reason} ->
{error, Reason}
end.
workspaces_request_no_update(Config, Operation, Body) ->
Payload = case Body of
[] -> <<"{}">>;
_ -> jsx:encode(lists:flatten(Body))
end,
Headers = headers(Config, Operation, Payload),
Request = #aws_request{service = workspaces,
uri = uri(Config),
method = post,
request_headers = Headers,
request_body = Payload},
case erlcloud_aws:request_to_return(erlcloud_retry:request(Config, Request, fun workspaces_result_fun/1)) of
{ok, {_RespHeaders, <<>>}} -> {ok, []};
{ok, {_RespHeaders, RespBody}} -> {ok, jsx:decode(RespBody, [{return_maps, false}])};
{error, _} = Error-> Error
end.
-spec workspaces_result_fun(Request :: aws_request()) -> aws_request().
workspaces_result_fun(#aws_request{response_type = ok} = Request) ->
Request;
workspaces_result_fun(#aws_request{response_type = error,
error_type = aws,
response_status = Status} = Request) when Status >= 500 ->
Request#aws_request{should_retry = true};
workspaces_result_fun(#aws_request{response_type = error, error_type = aws} = Request) ->
Request#aws_request{should_retry = false}.
headers(Config, Operation, Body) ->
Headers = [{"host", Config#aws_config.workspaces_host},
{"x-amz-target", lists:append(["WorkspacesService.", Operation])},
{"content-type", "application/x-amz-json-1.1"}],
Region = erlcloud_aws:aws_region_from_host(Config#aws_config.workspaces_host),
erlcloud_aws:sign_v4_headers(Config, Headers, Body, Region, "workspaces").
uri(#aws_config{workspaces_scheme = Scheme, workspaces_host = Host} = Config) ->
lists:flatten([Scheme, Host, port_spec(Config)]).
port_spec(#aws_config{workspaces_port=80}) ->
"";
port_spec(#aws_config{workspaces_port=Port}) ->
[":", erlang:integer_to_list(Port)].
encode_json_value(undefined) -> undefined;
encode_json_value(true) -> true;
encode_json_value(false) -> false;
encode_json_value(L) when is_list(L), is_list(hd(L)) -> [encode_json_value(V) || V <- L];
encode_json_value(L) when is_list(L), is_binary(hd(L)) -> [encode_json_value(V) || V <- L];
encode_json_value(L) when is_list(L) -> list_to_binary(L);
encode_json_value(B) when is_binary(B) -> B;
encode_json_value(A) when is_atom(A) -> atom_to_binary(A, latin1).