R/config.R

Defines functions load_profile get_config_file get_config_dir cloudos.profile_list cloudos.configure

Documented in cloudos.configure cloudos.profile_list get_config_dir get_config_file load_profile

#' Configure Lifebit Platform Profile
#'
#' Stores API credentials and workspace context for a named profile.
#' This is the required first step before any API wrapper call.
#'
#' @param profilename Character. Name of the profile to create or update.
#' @param apikey Character. API key for authentication.
#' @param workspace_id Character. Workspace/team ID for API requests.
#' @param base_url Character. Base URL for Lifebit Platform API (default: "https://cloudos.lifebit.ai").
#' @param set_default Logical. If TRUE, sets this profile as the default (default: FALSE).
#'
#' @return Invisible NULL. Prints a success message.
#' @export
#'
#' @examples
#' \dontrun{
#'   cloudos.configure(
#'     profilename = "production",
#'     apikey = "your-api-key",
#'     workspace_id = "5c6d3e9bd954e800b23f8c62",
#'     set_default = TRUE
#'   )
#' }
cloudos.configure <- function(profilename = "", 
                              apikey = "", 
                              workspace_id = "",
                              base_url = "https://cloudos.lifebit.ai",
                              set_default = FALSE) {
  
  # Validate required inputs
  if (profilename == "" || is.null(profilename)) {
    stop("Error: profilename is required and cannot be empty.", call. = FALSE)
  }
  
  if (apikey == "" || is.null(apikey)) {
    stop("Error: apikey is required and cannot be empty.", call. = FALSE)
  }
  
  if (workspace_id == "" || is.null(workspace_id)) {
    stop("Error: workspace_id is required and cannot be empty.", call. = FALSE)
  }
  
  # Get config directory path
  config_dir <- get_config_dir()
  
  # Ensure config directory exists
  if (!dir.exists(config_dir)) {
    dir.create(config_dir, recursive = TRUE, mode = "0700")
  }
  
  # Get config file path
  config_file <- get_config_file()
  
  # Read existing config or create new
  if (file.exists(config_file)) {
    config_json <- tryCatch({
      jsonlite::fromJSON(config_file, simplifyVector = FALSE)
    }, error = function(e) {
      list()
    })
  } else {
    config_json <- list()
  }
  
  # If setting as default, unset default flag from other profiles
  if (set_default) {
    for (pname in names(config_json)) {
      if (!is.null(config_json[[pname]]$default)) {
        config_json[[pname]]$default <- FALSE
      }
    }
  }
  
  # Add or update profile
  config_json[[profilename]] <- list(
    apikey = apikey,
    workspace_id = workspace_id,
    base_url = base_url,
    default = set_default,
    created_at = format(Sys.time(), "%Y-%m-%d %H:%M:%S"),
    updated_at = format(Sys.time(), "%Y-%m-%d %H:%M:%S")
  )
  
  # Write config file
  tryCatch({
    jsonlite::write_json(
      config_json, 
      config_file, 
      pretty = TRUE, 
      auto_unbox = TRUE
    )
    
    # Set restrictive permissions on config file (user read/write only)
    Sys.chmod(config_file, mode = "0600")
    
    if (set_default) {
      message(sprintf("Profile '%s' configured successfully and set as default.", profilename))
    } else {
      message(sprintf("Profile '%s' configured successfully.", profilename))
    }
    message(sprintf("Config stored at: %s", config_file))
    
    invisible(NULL)
    
  }, error = function(e) {
    stop(sprintf("Error writing config file: %s", e$message), call. = FALSE)
  })
}


#' List Lifebit Platform Profiles
#'
#' Lists configured profiles so users can confirm available environments.
#'
#' @return Data frame with profile names and metadata (workspace_id, default, created_at, updated_at).
#'         Returns empty data frame if no profiles are configured.
#' @export
#'
#' @examples
#' \dontrun{
#'   profiles <- cloudos.profile_list()
#'   print(profiles)
#' }
cloudos.profile_list <- function() {
  
  config_file <- get_config_file()
  
  # Check if config file exists
  if (!file.exists(config_file)) {
    message("No profiles configured. Use cloudos.configure() to create a profile.")
    return(data.frame(
      profile_name = character(0),
      workspace_id = character(0),
      base_url = character(0),
      default = logical(0),
      created_at = character(0),
      updated_at = character(0),
      stringsAsFactors = FALSE
    ))
  }
  
  # Read config
  config_json <- tryCatch({
    jsonlite::fromJSON(config_file, simplifyVector = FALSE)
  }, error = function(e) {
    stop(sprintf("Error reading config file: %s", e$message), call. = FALSE)
  })
  
  # Check if config is empty
  if (length(config_json) == 0) {
    message("No profiles configured. Use cloudos.configure() to create a profile.")
    return(data.frame(
      profile_name = character(0),
      workspace_id = character(0),
      base_url = character(0),
      default = logical(0),
      created_at = character(0),
      updated_at = character(0),
      stringsAsFactors = FALSE
    ))
  }
  
  # Extract profile metadata
  profiles <- lapply(names(config_json), function(profile_name) {
    profile <- config_json[[profile_name]]
    data.frame(
      profile_name = profile_name,
      workspace_id = profile$workspace_id %||% "",
      base_url = profile$base_url %||% "https://cloudos.lifebit.ai",
      default = profile$default %||% FALSE,
      created_at = profile$created_at %||% "",
      updated_at = profile$updated_at %||% "",
      stringsAsFactors = FALSE
    )
  })
  
  # Combine into single data frame
  profiles_df <- do.call(rbind, profiles)
  
  return(profiles_df)
}


#' Get Config Directory Path
#'
#' Internal function to get the Lifebit Platform config directory path.
#' Uses tools::R_user_dir() for CRAN-compliant persistent storage.
#' Can be overridden with CLOUDOS_CONFIG_DIR environment variable.
#'
#' @return Character. Path to config directory.
#' @keywords internal
get_config_dir <- function() {
  config_dir <- Sys.getenv("CLOUDOS_CONFIG_DIR", "")
  
  if (config_dir == "") {
    # Use R's user config directory (CRAN-compliant)
    config_dir <- tools::R_user_dir("cloudosR", which = "config")
  }
  
  return(config_dir)
}


#' Get Config File Path
#'
#' Internal function to get the Lifebit Platform config file path.
#' Config file is stored in the user's R config directory as .cloudos_config.json
#' using tools::R_user_dir() for CRAN compliance.
#'
#' @return Character. Path to config file.
#' @keywords internal
get_config_file <- function() {
  return(file.path(get_config_dir(), ".cloudos_config.json"))
}


#' Load Profile Configuration
#'
#' Internal function to load a specific profile's configuration.
#' If profilename is empty or NULL, loads the default profile.
#'
#' @param profilename Character. Name of the profile to load. If empty, loads default profile.
#'
#' @return List with profile configuration (apikey, workspace_id, base_url).
#' @keywords internal
load_profile <- function(profilename = "") {
  
  # If no profile specified, try to load default
  load_default <- (profilename == "" || is.null(profilename))
  
  config_file <- get_config_file()
  
  if (!file.exists(config_file)) {
    stop(sprintf(
      "No configuration file found. Use cloudos.configure() to create a profile first.\nExpected location: %s",
      config_file
    ), call. = FALSE)
  }
  
  # Read config
  config_json <- tryCatch({
    jsonlite::fromJSON(config_file, simplifyVector = FALSE)
  }, error = function(e) {
    stop(sprintf("Error reading config file: %s", e$message), call. = FALSE)
  })
  
  # If loading default profile, find it
  if (load_default) {
    default_profile <- NULL
    for (pname in names(config_json)) {
      if (isTRUE(config_json[[pname]]$default)) {
        default_profile <- pname
        profilename <- pname
        break
      }
    }
    
    if (is.null(default_profile)) {
      available_profiles <- paste(names(config_json), collapse = ", ")
      stop(sprintf(
        "No default profile configured.\nAvailable profiles: %s\nSpecify profilename or set a default with cloudos.configure(..., set_default = TRUE)",
        available_profiles
      ), call. = FALSE)
    }
  } else {
    # Check if specified profile exists
    if (!profilename %in% names(config_json)) {
      available_profiles <- paste(names(config_json), collapse = ", ")
      stop(sprintf(
        "Profile '%s' not found.\nAvailable profiles: %s\nUse cloudos.configure() to create this profile.",
        profilename,
        available_profiles
      ), call. = FALSE)
    }
  }
  
  profile <- config_json[[profilename]]
  
  # Validate profile has required fields
  if (is.null(profile$apikey) || profile$apikey == "") {
    stop(sprintf("Profile '%s' is missing apikey.", profilename), call. = FALSE)
  }
  
  if (is.null(profile$workspace_id) || profile$workspace_id == "") {
    stop(sprintf("Profile '%s' is missing workspace_id.", profilename), call. = FALSE)
  }
  
  # Set default base_url if not present
  if (is.null(profile$base_url) || profile$base_url == "") {
    profile$base_url <- "https://cloudos.lifebit.ai"
  }
  
  return(profile)
}

Try the cloudosR package in your browser

Any scripts or data that you put into this service are public.

cloudosR documentation built on June 1, 2026, 5:07 p.m.